This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.
#***************************************************************************************************************************************
# Functions: Start
#***************************************************************************************************************************************
#===============================================================================
# PCA
#===============================================================================
multisample_PCA = function(df, sampleinfo_df, outfile)
{
tempdf = df[,-1];
tempcol = colnames(tempdf);
tempgrp = sampleinfo_df[tempcol,2];
tempdf = t(tempdf) %>% as.data.frame();
tempdf[is.na(tempdf)] = 0;
tempdf$Group = tempgrp;
png(outfile, width = 6, height = 6, units = 'in', res=300);
# bitmap(outfile, "png16m");
g = autoplot(prcomp(select(tempdf, -Group)), data = tempdf, colour = 'Group', size=3);
plot(g);
dev.off();
}
#===============================================================================
# Regression and Cook's distance
#===============================================================================
singlesample_regression = function(PE_TE_data,htmloutfile, append=TRUE)
{
rownames(PE_TE_data) = PE_TE_data$PE_ID;
regmodel = lm(PE_abundance~TE_abundance, data=PE_TE_data);
regmodel_summary = summary(regmodel);
cat("<font><h3>Linear Regression model fit between Proteome and Transcriptome data</h3></font>\n",
"<p>Assuming a linear relationship between Proteome and Transcriptome data, we here fit a linear regression model.</p>\n",
'<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Parameter</font></th><th><font color=#ffcc33>Value</font></th></tr>\n',
file = htmloutfile, append = TRUE);
cat("<tr><td>Formula</td><td>","PE_abundance~TE_abundance","</td></tr>\n",
"<tr><td colspan='2' align='center'> <b>Coefficients</b></td>","</tr>\n",
"<tr><td>",names(regmodel$coefficients[1]),"</td><td>",regmodel$coefficients[1]," (Pvalue:", regmodel_summary$coefficients[1,4],")","</td></tr>\n",
"<tr><td>",names(regmodel$coefficients[2]),"</td><td>",regmodel$coefficients[2]," (Pvalue:", regmodel_summary$coefficients[2,4],")","</td></tr>\n",
"<tr><td colspan='2' align='center'> <b>Model parameters</b></td>","</tr>\n",
"<tr><td>Residual standard error</td><td>",regmodel_summary$sigma," (",regmodel_summary$df[2]," degree of freedom)</td></tr>\n",
"<tr><td>F-statistic</td><td>",regmodel_summary$fstatistic[1]," ( on ",regmodel_summary$fstatistic[2]," and ",regmodel_summary$fstatistic[3]," degree of freedom)</td></tr>\n",
"<tr><td>R-squared</td><td>",regmodel_summary$r.squared,"</td></tr>\n",
"<tr><td>Adjusted R-squared</td><td>",regmodel_summary$adj.r.squared,"</td></tr>\n",
file = htmloutfile, append = TRUE);
cat("</table>\n", file = htmloutfile, append = TRUE);
cat(
"<font color='#ff0000'><h3>Regression and diagnostics plots</h3></font>\n",
file = htmloutfile, append = TRUE);
outplot = paste(outdir,"/PE_TE_lm_1.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in',res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(regmodel, 1, cex.lab=1.5);
dev.off();
outplot = paste(outdir,"/PE_TE_lm_2.png",sep="",collapse="");
png(outplot,width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(regmodel, 2, cex.lab=1.5);
dev.off();
outplot = paste(outdir,"/PE_TE_lm_5.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in',res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(regmodel, 5, cex.lab=1.5);
dev.off();
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; ">', file = htmloutfile, append = TRUE);
cat(
'<tr bgcolor="#7a0019"><th>', "<font color='#ffcc33'><h4>1) <u>Residuals vs Fitted plot</h4></font></u></th>\n",
'<th><font color=#ffcc33><h4>2) <u>Normal Q-Q plot of residuals</h4></font></u></th></tr>\n',
file = htmloutfile, append = TRUE);
cat(
'<tr><td align=center><img src="PE_TE_lm_1.png" width=600 height=600></td><td align=center><img src="PE_TE_lm_2.png" width=600 height=600></td></tr>\n',
file = htmloutfile, append = TRUE);
cat(
'<tr><td align=center>This plot checks for linear relationship assumptions.<br>If a horizontal line is observed without any distinct patterns, it indicates a linear relationship.</td>\n',
'<td align=center>This plot checks whether residuals are normally distributed or not.<br>It is good if the residuals points follow the straight dashed line i.e., do not deviate much from dashed line.</td></tr></table>\n',
file = htmloutfile, append = TRUE);
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Residuals data
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
res_all = regmodel$residuals;
res_mean = mean(res_all);
res_sd = sd(res_all);
res_diff = (res_all-res_mean);
res_zscore = res_diff/res_sd;
# res_outliers = res_all[which((res_zscore > 2)|(res_zscore < -2))]
tempind = which((res_zscore > 2)|(res_zscore < -2));
res_PE_TE_data_no_outlier = PE_TE_data[-tempind,];
res_PE_TE_data_no_outlier$residuals = res_all[-tempind];
res_PE_TE_data_outlier = PE_TE_data[tempind,];
res_PE_TE_data_outlier$residuals = res_all[tempind];
# Save the complete table for download (influential_observations)
temp_outlier_data = data.frame(res_PE_TE_data_outlier$PE_ID, res_PE_TE_data_outlier$TE_abundance, res_PE_TE_data_outlier$PE_abundance, res_PE_TE_data_outlier$residuals)
colnames(temp_outlier_data) = c("Gene", "Transcript abundance", "Protein abundance", "Residual value")
outdatafile = paste(outdir,"/PE_TE_outliers_residuals.txt", sep="", collapse="");
write.table(temp_outlier_data, file=outdatafile, row.names=F, sep="\t", quote=F);
# Save the complete table for download (non influential_observations)
temp_all_data = data.frame(PE_TE_data$PE_ID, PE_TE_data$TE_abundance, PE_TE_data$PE_abundance, res_all)
colnames(temp_all_data) = c("Gene", "Transcript abundance", "Protein abundance", "Residual value")
outdatafile = paste(outdir,"/PE_TE_abundance_residuals.txt", sep="", collapse="");
write.table(temp_all_data, file=outdatafile, row.names=F, sep="\t", quote=F);
cat('<br><h2 id="inf_obs"><font color=#ff0000>Outliers based on the residuals from regression analysis</font></h2>\n',
file = htmloutfile, append = TRUE);
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; ">\n',
'<tr bgcolor="#7a0019"><th colspan=2><font color=#ffcc33>Residuals from Regression</font></th></tr>\n',
'<tr bgcolor="#7a0019"><th><font color=#ffcc33>Parameter</font></th><th><font color=#ffcc33>Value</font></th></tr>\n',
file = htmloutfile, append = TRUE);
cat("<tr><td>Mean Residual value</td><td>",res_mean,"</td></tr>\n",
"<tr><td>Standard deviation (Residuals)</td><td>",res_sd,"</td></tr>\n",
'<tr><td>Total outliers (Residual value > 2 standard deviation from the mean)</td><td>',length(tempind),' <font size=4>(<b><a href=PE_TE_outliers_residuals.txt target="_blank">Download these ',length(tempind),' data points with high residual values here</a></b>)</font></td>\n',
'<tr><td colspan=2 align=center><font size=4>(<b><a href=PE_TE_abundance_residuals.txt target="_blank">Download the complete residuals data here</a></b>)</font></td></td>\n',
"</table><br><br>\n",
file = htmloutfile, append = TRUE);
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
cat('<br><br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; ">', file = htmloutfile, append = TRUE);
cat(
'<tr bgcolor="#7a0019"><th><font color=#ffcc33><h4>3) <u>Residuals vs Leverage plot</h4></font></u></th></tr>\n',
file = htmloutfile, append = TRUE);
cat(
'<tr><td align=center><img src="PE_TE_lm_5.png" width=600 height=600></td></tr>\n',
file = htmloutfile, append = TRUE);
cat(
'<tr><td align=center>This plot is useful to identify any influential cases, that is outliers or extreme values.<br>They might influence the regression results upon inclusion or exclusion from the analysis.</td></tr></table><br>\n',
file = htmloutfile, append = TRUE);
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Cook's Distance
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cat('<hr/><h2 id="inf_obs"><font color=#ff0000>INFLUENTIAL OBSERVATIONS</font></h2>\n',
file = htmloutfile, append = TRUE);
cat(
'<p><b>Cook\'s distance</b> computes the influence of each data point/observation on the predicted outcome. i.e. this measures how much the observation is influencing the fitted values.<br>In general use, those observations that have a <b>Cook\'s distance > than ', cookdist_upper_cutoff,' times the mean</b> may be classified as <b>influential.</b></p>\n',
file = htmloutfile, append = TRUE);
cooksd <- cooks.distance(regmodel);
outplot = paste(outdir,"/PE_TE_lm_cooksd.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(cooksd, main="Influential Obs. by Cook\'s distance", ylab="Cook\'s distance", xlab="Observations", type="n") # plot cooks distance
sel_outlier=which(cooksd>=as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T))
sel_nonoutlier=which(cooksd<as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T))
points(sel_outlier, cooksd[sel_outlier],pch="*", cex=2, cex.lab=1.5, col="red")
points(sel_nonoutlier, cooksd[sel_nonoutlier],pch="*", cex=2, cex.lab=1.5, col="black")
abline(h = as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T), col="red") # add cutoff line
#text(x=1:length(cooksd)+1, y=cooksd, labels=ifelse(cooksd>as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T),names(cooksd),""), col="red", pos=2) # add labels
dev.off();
cat(
'<img src="PE_TE_lm_cooksd.png" width=800 height=800>',
'<br>In the above plot, observations above red line (',cookdist_upper_cutoff,' * mean Cook\'s distance) are influential. Genes that are outliers could be important. These observations influences the correlation values and regression coefficients<br><br>',
file = htmloutfile, append = TRUE);
tempind = which(cooksd>as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T));
PE_TE_data_no_outlier = PE_TE_data[-tempind,];
PE_TE_data_no_outlier$cooksd = cooksd[-tempind];
PE_TE_data_outlier = PE_TE_data[tempind,];
PE_TE_data_outlier$cooksd = cooksd[tempind];
a = sort(PE_TE_data_outlier$cooksd, decreasing=T, index.return=T);
PE_TE_data_outlier_sorted = PE_TE_data_outlier[a$ix,];
cat(
'<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Parameter</font></th><th><font color=#ffcc33>Value</font></th></tr>\n',
file = htmloutfile, append = TRUE);
# Save the complete table for download (influential_observations)
temp_outlier_data = data.frame(PE_TE_data_outlier$PE_ID, PE_TE_data_outlier$TE_abundance, PE_TE_data_outlier$PE_abundance, PE_TE_data_outlier$cooksd)
colnames(temp_outlier_data) = c("Gene", "Transcript abundance", "Protein abundance", "Cook's distance")
outdatafile = paste(outdir,"/PE_TE_influential_observation.txt", sep="", collapse="");
write.table(temp_outlier_data, file=outdatafile, row.names=F, sep="\t", quote=F);
# Save the complete table for download (non influential_observations)
temp_no_outlier_data = data.frame(PE_TE_data_no_outlier$PE_ID, PE_TE_data_no_outlier$TE_abundance, PE_TE_data_no_outlier$PE_abundance, PE_TE_data_no_outlier$cooksd)
colnames(temp_no_outlier_data) = c("Gene", "Transcript abundance", "Protein abundance", "Cook's distance")
outdatafile = paste(outdir,"/PE_TE_non_influential_observation.txt", sep="", collapse="");
write.table(temp_no_outlier_data, file=outdatafile, row.names=F, sep="\t", quote=F);
cat("<tr><td>Mean Cook\'s distance</td><td>",mean(cooksd, na.rm=T),"</td></tr>\n",
"<tr><td>Total influential observations (Cook\'s distance > ",cookdist_upper_cutoff," * mean Cook\'s distance)</td><td>",length(tempind),"</td>\n",
"<tr><td>Observations with Cook\'s distance < ",cookdist_upper_cutoff," * mean Cook\'s distance</td><td>",length(which(cooksd<as.numeric(cookdist_upper_cutoff)*mean(cooksd, na.rm=T))),"</td>\n",
"</table><br><br>\n",
file = htmloutfile, append = TRUE);
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
# Scatter plot after removal of influential points
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
outplot = paste(outdir,"/AbundancePlot_scatter_without_outliers.png",sep="",collapse="");
min_lim = min(c(PE_TE_data$PE_abundance,PE_TE_data$TE_abundance));
max_lim = max(c(PE_TE_data$PE_abundance,PE_TE_data$TE_abundance));
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot,"png16m");
g = ggplot(PE_TE_data_no_outlier, aes(x=TE_abundance, y=PE_abundance))+geom_point() + geom_smooth() + xlab("Transcript abundance log fold-change") + ylab("Protein abundance log fold-change") + xlim(min_lim,max_lim) + ylim(min_lim,max_lim);
suppressMessages(plot(g));
dev.off();
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Scatterplot: Before removal</font></th><th><font color=#ffcc33>Scatterplot: After removal</font></th></tr>\n', file = htmloutfile, append = TRUE);
# Before
cat("<tr><td align=center><!--<font color='#ff0000'><h3>Scatter plot between Proteome and Transcriptome Abundance</h3></font>\n-->", '<img src="TE_PE_scatter.png" width=600 height=600></td>\n', file = htmloutfile, append = TRUE);
# After
cat("<td align=center>\n",
'<img src="AbundancePlot_scatter_without_outliers.png" width=600 height=600></td></tr>\n',
file = htmloutfile, append = TRUE);
#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
cor_result_pearson = cor.test(PE_TE_data_no_outlier[,"TE_abundance"], PE_TE_data_no_outlier[,"PE_abundance"], method = "pearson");
cor_result_spearman = cor.test(PE_TE_data_no_outlier[,"TE_abundance"], PE_TE_data_no_outlier[,"PE_abundance"], method = "spearman");
cor_result_kendall = cor.test(PE_TE_data_no_outlier[,"TE_abundance"], PE_TE_data_no_outlier[,"PE_abundance"], method = "kendall");
cat('<tr><td>\n', file = htmloutfile, append=TRUE);
singlesample_cor(PE_TE_data, htmloutfile, append=TRUE);
cat('</td>\n', file = htmloutfile, append=TRUE);
cat('<td><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Parameter</font></th><th><font color=#ffcc33>Method 1</font></th><th><font color=#ffcc33>Method 2</font></th><th><font color=#ffcc33>Method 3</font></th></tr>\n',
file = htmloutfile, append = TRUE);
cat(
"<tr><td>Correlation method</td><td>",cor_result_pearson$method,"</td><td>",cor_result_spearman$method,"</td><td>",cor_result_kendall$method,"</td></tr>\n",
"<tr><td>Correlation coefficient</td><td>",cor_result_pearson$estimate,"</td><td>",cor_result_spearman$estimate,"</td><td>",cor_result_kendall$estimate,"</td></tr>\n",
file = htmloutfile, append = TRUE)
cat("</table></td></tr></table>\n", file = htmloutfile, append = TRUE)
if(dim(PE_TE_data_outlier)[1]<10)
{
tab_n_row = dim(PE_TE_data_outlier)[1];
}else{
tab_n_row = 10;
}
cat("<br><br><font size=5><b><a href='PE_TE_influential_observation.txt' target='_blank'>Download the complete list of influential observations</a></b></font> ",
"<font size=5><b><a href='PE_TE_non_influential_observation.txt' target='_blank'>Download the complete list (After removing influential points)</a></b></font><br>\n",
'<br><font color="brown"><h4>Top ',as.character(tab_n_row),' Influential observations (Cook\'s distance > ',cookdist_upper_cutoff,' * mean Cook\'s distance)</h4></font>\n',
file = htmloutfile, append = TRUE);
cat('<table border=1 cellspacing=0 cellpadding=5> <tr bgcolor="#7a0019">\n', sep = "",file = htmloutfile, append = TRUE);
cat("<th><font color=#ffcc33>Gene</font></th><th><font color=#ffcc33>Protein Log Fold-Change</font></th><th><font color=#ffcc33>Transcript Log Fold-Change</font></th><th><font color=#ffcc33>Cook's Distance</font></th></tr>\n",
file = htmloutfile, append = TRUE);
for(i in 1:tab_n_row)
{
cat(
'<tr>','<td>',as.character(PE_TE_data_outlier_sorted[i,1]),'</td>\n',
'<td>',format(PE_TE_data_outlier_sorted[i,2], scientific=F),'</td>\n',
'<td>',PE_TE_data_outlier_sorted[i,4],'</td>\n',
'<td>',format(PE_TE_data_outlier_sorted[i,5], scientific=F),'</td></tr>\n',
file = htmloutfile, append = TRUE);
}
cat('</table><br><br>\n',file = htmloutfile, append = TRUE);
}
#===============================================================================
# Heatmap
#===============================================================================
singlesample_heatmap=function(PE_TE_data, htmloutfile, hm_nclust){
cat('<br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Heatmap of PE and TE abundance values (Hierarchical clustering)</font></th><th><font color=#ffcc33>Number of clusters to extract: ',hm_nclust,'</font></th></tr>\n',
file = htmloutfile, append = TRUE);
hc=hclust(dist(as.matrix(PE_TE_data[,c("PE_abundance","TE_abundance")])))
hm_cluster = cutree(hc,k=hm_nclust);
outplot = paste(outdir,"/PE_TE_heatmap.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
hmap = heatmap.2(as.matrix(PE_TE_data[,c("PE_abundance","TE_abundance")]), trace="none", cexCol=1, col=greenred(100),Colv=F, labCol=c("Proteins","Transcripts"), scale="col", hclustfun = hclust, distfun = dist);
dev.off();
cat('<tr><td align=center colspan="2"><img src="PE_TE_heatmap.png" width=800 height=800></td></tr>\n',
file = htmloutfile, append = TRUE);
temp_PE_TE_data = data.frame(PE_TE_data$PE_ID, PE_TE_data$TE_abundance, PE_TE_data$PE_abundance, hm_cluster);
colnames(temp_PE_TE_data) = c("Gene", "Transcript abundance", "Protein abundance", "Cluster (Hierarchical clustering)")
tempoutfile = paste(outdir,"/PE_TE_hc_clusterpoints.txt",sep="",collapse="");
write.table(temp_PE_TE_data, file=tempoutfile, row.names=F, quote=F, sep="\t", eol="\n")
cat('<tr><td colspan="2" align=center><font size=5><a href="PE_TE_hc_clusterpoints.txt" target="_blank"><b>Download the hierarchical cluster list</b></a></font></td></tr></table>\n',
file = htmloutfile, append = TRUE);
}
#===============================================================================
# K-means clustering
#===============================================================================
singlesample_kmeans=function(PE_TE_data, htmloutfile, nclust){
PE_TE_data_kdata = PE_TE_data;
k1 = kmeans(PE_TE_data_kdata[,c("PE_abundance","TE_abundance")], nclust);
outplot = paste(outdir,"/PE_TE_kmeans.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
scatter.smooth(PE_TE_data_kdata[,"TE_abundance"], PE_TE_data_kdata[,"PE_abundance"], xlab="Transcript Abundance", ylab="Protein Abundance", cex.lab=1.5);
legend(1, 95, legend=c("Cluster 1", "Line 2"), col="red", lty=1:1, cex=0.8)
legend(1, 95, legend="Cluster 2", col="green", lty=1:1, cex=0.8)
ind=which(k1$cluster==1);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="red", pch=16);
ind=which(k1$cluster==2);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="green", pch=16);
ind=which(k1$cluster==3);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="blue", pch=16);
ind=which(k1$cluster==4);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="cyan", pch=16);
ind=which(k1$cluster==5);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="black", pch=16);
ind=which(k1$cluster==6);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="brown", pch=16);
ind=which(k1$cluster==7);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="gold", pch=16);
ind=which(k1$cluster==8);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="thistle", pch=16);
ind=which(k1$cluster==9);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="yellow", pch=16);
ind=which(k1$cluster==10);
points(PE_TE_data_kdata[ind,"TE_abundance"], PE_TE_data_kdata[ind,"PE_abundance"], col="orange", pch=16);
dev.off();
cat('<br><br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>K-mean clustering</font></th><th><font color=#ffcc33>Number of clusters: ',nclust,'</font></th></tr>\n',
file = htmloutfile, append = TRUE);
tempind = order(k1$cluster);
tempoutfile = paste(outdir,"/PE_TE_kmeans_clusterpoints.txt",sep="",collapse="");
write.table(data.frame(PE_TE_data_kdata[tempind, ], Cluster=k1$cluster[tempind]), file=tempoutfile, row.names=F, quote=F, sep="\t", eol="\n")
cat('<tr><td colspan="2" align=center><img src="PE_TE_kmeans.png" width=800 height=800></td></tr>\n',
file = htmloutfile, append = TRUE);
cat('<tr><td colspan="2" align=center><font size=5><a href="PE_TE_kmeans_clusterpoints.txt" target="_blank"><b>Download the cluster list</b></a></font></td></tr></table><br><hr/>\n',
file = htmloutfile, append = TRUE);
}
#===============================================================================
# scatter plot
#===============================================================================
singlesample_scatter = function(PE_TE_data, outfile)
{
min_lim = min(c(PE_TE_data$PE_abundance,PE_TE_data$TE_abundance));
max_lim = max(c(PE_TE_data$PE_abundance,PE_TE_data$TE_abundance));
png(outfile, width = 10, height = 10, units = 'in', res=300);
# bitmap(outfile, "png16m");
g = ggplot(PE_TE_data, aes(x=TE_abundance, y=PE_abundance))+geom_point() + geom_smooth() + xlab("Transcript abundance log fold-change") + ylab("Protein abundance log fold-change") + xlim(min_lim,max_lim) + ylim(min_lim,max_lim);
suppressMessages(plot(g));
# plot(g);
dev.off();
}
#===============================================================================
# Correlation table
#===============================================================================
singlesample_cor = function(PE_TE_data, htmloutfile, append=TRUE)
{
cor_result_pearson = cor.test(PE_TE_data$TE_abundance, PE_TE_data$PE_abundance, method = "pearson");
cor_result_spearman = cor.test(PE_TE_data$TE_abundance, PE_TE_data$PE_abundance, method = "spearman");
cor_result_kendall = cor.test(PE_TE_data$TE_abundance, PE_TE_data$PE_abundance, method = "kendall");
cat(
'<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Parameter</font></th><th><font color=#ffcc33>Method 1</font></th><th><font color=#ffcc33>Method 2</font></th><th><font color=#ffcc33>Method 3</font></th></tr>\n',
file = htmloutfile, append = TRUE);
cat(
"<tr><td>Correlation method</td><td>",cor_result_pearson$method,"</td><td>",cor_result_spearman$method,"</td><td>",cor_result_kendall$method,"</td></tr>\n",
"<tr><td>Correlation coefficient</td><td>",cor_result_pearson$estimate,"</td><td>",cor_result_spearman$estimate,"</td><td>",cor_result_kendall$estimate,"</td></tr>\n",
file = htmloutfile, append = TRUE)
cat("</table>\n", file = htmloutfile, append = TRUE);
}
#===============================================================================
# Boxplot
#===============================================================================
multisample_boxplot = function(df, sampleinfo_df, outfile, fill_leg, user_xlab, user_ylab)
{
tempdf = df[,-1, drop=FALSE];
tempdf = t(tempdf) %>% as.data.frame();
tempdf[is.na(tempdf)] = 0;
tempdf$Sample = rownames(tempdf);
tempdf1 = melt(tempdf, id.vars = "Sample");
tempdf1$Group = sampleinfo_df[tempdf1$Sample,2];
png(outplot, width = 6, height = 6, units = 'in', res=300);
# bitmap(outplot, "png16m");
if(fill_leg=="Yes")
{
g = ggplot(tempdf1, aes(x=Sample, y=value, fill=Group)) + geom_boxplot() + labs(x=user_xlab) + labs(y=user_ylab)
}else{
if(fill_leg=="No")
{
tempdf1$Group = c("case", "control")
g = ggplot(tempdf1, aes(x=Sample, y=value, fill=Group)) + geom_boxplot() + labs(x=user_xlab) + labs(y=user_ylab)
}
}
plot(g);
#htmlwidgets::saveWidget(g, "index.html")
dev.off();
}
#===============================================================================
# Mean or Median of Replicates
#===============================================================================
mergeReplicates = function(TE_df,PE_df, sampleinfo_df, method)
{
grps = unique(sampleinfo_df[,2]);
TE_df_merged <<- sapply(grps, function(x){
tempsample = sampleinfo_df[which(sampleinfo_df$Group==x),1]
if(length(tempsample)!=1){
apply(TE_df[,tempsample],1,method);
}else{
return(TE_df[,tempsample]);
}
});
TE_df_merged <<- data.frame(as.character(TE_df[,1]), TE_df_merged);
colnames(TE_df_merged) = c(colnames(TE_df)[1], grps);
PE_df_merged <<- sapply(grps, function(x){
tempsample = sampleinfo_df[which(sampleinfo_df$Group==x),1]
if(length(tempsample)!=1){
apply(PE_df[,tempsample],1,method);
}else{
return(PE_df[,tempsample]);
}
});
PE_df_merged <<- data.frame(as.character(PE_df[,1]), PE_df_merged);
colnames(PE_df_merged) = c(colnames(PE_df)[1], grps);
#sampleinfo_df_merged = data.frame(Sample = grps, Group = grps, stringsAsFactors = F);
sampleinfo_df_merged = data.frame(Sample = grps, Group = "Group", stringsAsFactors = F);
return(list(TE_df_merged = TE_df_merged, PE_df_merged = PE_df_merged, sampleinfo_df_merged = sampleinfo_df_merged));
}
#===============================================================================
# (T-Test or Wilcoxon ranksum test) and Volcano Plot
#===============================================================================
perform_Test_Volcano = function(TE_df_data,PE_df_data,TE_df_logfold, PE_df_logfold,sampleinfo_df, method, correction_method,volc_with)
{
PE_colnames = colnames(PE_df_data);
control_sample = sampleinfo_df[which(sampleinfo_df$Group=="control"),1];
control_ind <<- sapply(control_sample, function(x){temp_ind = which(PE_colnames==x); as.numeric(temp_ind)});
condition_sample = sampleinfo_df[which(sampleinfo_df$Group=="case"),1];
condition_ind <<- sapply(condition_sample, function(x){temp_ind = which(PE_colnames==x); as.numeric(temp_ind)});
if(method=="mean"){
#PE_pval = apply(PE_df_data[2:length(colnames(PE_df_data))],1,function(x) t.test(x[condition_ind-1], x[control_ind-1])$p.value);
PE_pval = apply(PE_df_data[2:length(colnames(PE_df_data))],1,function(x) {obj<-try(t.test(x[condition_ind-1], x[control_ind-1]),silent=TRUE); if(is(obj, "try-error")){return(NA)}else{return(obj$p.value)}})
}else{
if(method=="median"){
PE_pval = apply(PE_df_data[2:length(colnames(PE_df_data))],1,function(x) {obj<-try(wilcox.test(x[condition_ind-1], x[control_ind-1]),silent=TRUE); if(is(obj, "try-error")){return(NA)}else{return(obj$p.value)}})
# PE_pval = apply(PE_df_data[2:length(colnames(PE_df_data))],1,function(x) wilcox.test(x[condition_ind-1], x[control_ind-1])$p.value);
}
}
PE_adj_pval = p.adjust(PE_pval, method = correction_method, n = length(PE_pval))
TE_colnames = colnames(TE_df_data);
control_sample = sampleinfo_df[which(sampleinfo_df$Group=="control"),1];
control_ind <<- sapply(control_sample, function(x){temp_ind = which(TE_colnames==x); as.numeric(temp_ind)});
condition_sample = sampleinfo_df[which(sampleinfo_df$Group=="case"),1];
condition_ind <<- sapply(condition_sample, function(x){temp_ind = which(TE_colnames==x); as.numeric(temp_ind)});
if(method=="mean"){
# TE_pval = apply(TE_df_data[2:length(colnames(TE_df_data))],1,function(x) t.test(x[condition_ind-1], x[control_ind-1])$p.value);
TE_pval = apply(TE_df_data[2:length(colnames(TE_df_data))],1,function(x) {obj<-try(t.test(x[condition_ind-1], x[control_ind-1]),silent=TRUE); if(is(obj, "try-error")){return(NA)}else{return(obj$p.value)}})
}else{
if(method=="median"){
TE_pval = apply(TE_df_data[2:length(colnames(TE_df_data))],1,function(x) {obj<-try(wilcox.test(x[condition_ind-1], x[control_ind-1]),silent=TRUE); if(is(obj, "try-error")){return(NA)}else{return(obj$p.value)}})
# TE_pval = apply(TE_df_data[2:length(colnames(TE_df_data))],1,function(x) wilcox.test(x[condition_ind-1], x[control_ind-1])$p.value);
}
}
TE_adj_pval = p.adjust(TE_pval, method = correction_method, n = length(TE_pval))
PE_TE_logfold_pval = data.frame(TE_df_logfold$Gene, TE_df_logfold$LogFold, TE_pval, TE_adj_pval, PE_df_logfold$LogFold, PE_pval, PE_adj_pval);
colnames(PE_TE_logfold_pval) = c("Gene", "Transcript log fold-change", "p-value (transcript)", "adj p-value (transcript)", "Protein log fold-change", "p-value (protein)", "adj p-value (protein)");
outdatafile = paste(outdir,"/PE_TE_logfold_pval.txt", sep="", collapse="");
write.table(PE_TE_logfold_pval, file=outdatafile, row.names=F, sep="\t", quote=F);
cat("<br><br><font size=5><b><a href='PE_TE_logfold_pval.txt' target='_blank'>Download the complete fold change data here</a></b></font><br>\n",
file = htmloutfile, append = TRUE);
if(length(condition_ind)!=1)
{
# Volcano Plot
if(volc_with=="adj_pval")
{
PE_pval = PE_adj_pval
TE_pval = TE_adj_pval
volc_ylab = "-log10 Adjusted p-value";
}else{
if(volc_with=="pval")
{
volc_ylab = "-log10 p-value";
}
}
outplot = paste(outdir,"/PE_volcano.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(PE_df_logfold$LogFold, -log10(PE_pval),
xlab="log2 fold change", ylab=volc_ylab,
type="n")
sel <- which((PE_df_logfold$LogFold<=log(2,base=2))&(PE_df_logfold$LogFold>=log(0.5, base=2))) # or whatever you want to use
points(PE_df_logfold[sel,"LogFold"], -log10(PE_pval[sel]),col="black")
#sel <- which((PE_df_logfold$LogFold>log(2,base=2))&(PE_df_logfold$LogFold<log(0.5,base=2))) # or whatever you want to use
sel <- which((PE_df_logfold$LogFold>log(2,base=2))|(PE_df_logfold$LogFold<log(0.5, base=2)))
sel1 <- which(PE_pval<=0.05)
sel=intersect(sel,sel1)
points(PE_df_logfold[sel,"LogFold"], -log10(PE_pval[sel]),col="red")
sel <- which((PE_df_logfold$LogFold>log(2,base=2))|(PE_df_logfold$LogFold<log(0.5, base=2)))
sel1 <- which(PE_pval>0.05)
sel=intersect(sel,sel1)
points(PE_df_logfold[sel,"LogFold"], -log10(PE_pval[sel]),col="blue")
abline(h = -log(0.05,base=10), col="red", lty=2)
abline(v = log(2,base=2), col="red", lty=2)
abline(v = log(0.5,base=2), col="red", lty=2)
dev.off();
outplot = paste(outdir,"/TE_volcano.png",sep="",collapse="");
png(outplot, width = 10, height = 10, units = 'in', res=300);
# bitmap(outplot, "png16m");
par(mfrow=c(1,1));
plot(TE_df_logfold$LogFold, -log10(TE_pval),
xlab="log2 fold change", ylab=volc_ylab,
type="n")
sel <- which((TE_df_logfold$LogFold<=log(2,base=2))&(TE_df_logfold$LogFold>=log(0.5, base=2))) # or whatever you want to use
points(TE_df_logfold[sel,"LogFold"], -log10(TE_pval[sel]),col="black")
#sel <- which((TE_df_logfold$LogFold>log(2,base=2))&(TE_df_logfold$LogFold<log(0.5,base=2))) # or whatever you want to use
sel <- which((TE_df_logfold$LogFold>log(2,base=2))|(TE_df_logfold$LogFold<log(0.5, base=2)))
sel1 <- which(TE_pval<=0.05)
sel=intersect(sel,sel1)
points(TE_df_logfold[sel,"LogFold"], -log10(TE_pval[sel]),col="red")
sel <- which((TE_df_logfold$LogFold>log(2,base=2))|(TE_df_logfold$LogFold<log(0.5, base=2)))
sel1 <- which(TE_pval>0.05)
sel=intersect(sel,sel1)
points(TE_df_logfold[sel,"LogFold"], -log10(TE_pval[sel]),col="blue")
abline(h = -log(0.05,base=10), col="red", lty=2)
abline(v = log(2,base=2), col="red", lty=2)
abline(v = log(0.5,base=2), col="red", lty=2)
dev.off();
cat('<br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Transcript Fold-Change</font></th><th><font color=#ffcc33>Protein Fold-Change</font></th></tr>\n', file = htmloutfile, append = TRUE);
cat("<tr><td align=center>", '<img src="TE_volcano.png" width=600 height=600></td>\n', file = htmloutfile, append = TRUE);
cat("<td align=center>",
'<img src="PE_volcano.png" width=600 height=600></td></tr></table><br>\n',
file = htmloutfile, append = TRUE);
}else{
cat('<br><br><b><font color=red>!!! No replicates found. Cannot perform test to check significance of differential expression. Thus, no Volcano plot generated !!!</font></b><br><br>',
file = htmloutfile, append = TRUE);
}
}
#***************************************************************************************************************************************
# Functions: End
#***************************************************************************************************************************************
p <- plot_ly(y = ~rnorm(50), type ="box") %>%
add_trace(y = ~rnorm(50, 1))
p
htmlwidgets::saveWidget(as_widget(p), "boxplot-test.html")
#===============================================================================
# Boxplot
#===============================================================================
multisample_boxplot = function(df, sampleinfo_df, outfile, fill_leg, user_xlab, user_ylab)
{
tempdf = df[,-1, drop=FALSE];
tempdf = t(tempdf) %>% as.data.frame();
tempdf[is.na(tempdf)] = 0;
tempdf$Sample = rownames(tempdf);
tempdf1 = melt(tempdf, id.vars = "Sample");
tempdf1$Group = sampleinfo_df[tempdf1$Sample,2];
png(outplot, width = 6, height = 6, units = 'in', res=300);
# bitmap(outplot, "png16m");
if(fill_leg=="Yes")
{
g = ggplot(tempdf1, aes(x=Sample, y=value, fill=Group)) + geom_boxplot() + labs(x=user_xlab) + labs(y=user_ylab)
p <- plot_ly(y = tempdf1)
}else{
if(fill_leg=="No")
{
tempdf1$Group = c("case", "control")
g = ggplot(tempdf1, aes(x=Sample, y=value, fill=Group)) + geom_boxplot() + labs(x=user_xlab) + labs(y=user_ylab)
p <- plot_ly(y = ~rnorm(50), type ="box") %>%
add_trace(y = ~rnorm(50, 1))
htmlwidgets::saveWidget(as_widget(p), "boxplot-test.html")
}
}
plot(g);
dev.off();
}
#===============================================================================
# Arguments
#===============================================================================
noargs = 12;
#args = commandArgs(trailingOnly = TRUE);
args = c('multiple',
'mean',
'test-data/exp_design_file.tabular',
'test-data/protein_data.tabular',
'test-data/transcript_data.tabular',
'BH',
4,
4,
5,
'pval',
'test-files/test-output.html',
'test-files/')
if(length(args) != noargs)
{
stop(paste("Please check usage. Number of arguments is not equal to ",noargs,sep="",collapse=""));
}
mode = args[1]; # "multiple" or "logfold"
method = args[2]; # "mean" or "median"
sampleinfo_file = args[3];
proteome_file = args[4];
transcriptome_file = args[5];
correction_method = args[6];
cookdist_upper_cutoff = args[7];
numCluster = args[8];
hm_nclust = args[9];
volc_with = args[10];
htmloutfile = args[11]; # html output file
outdir = args[12]; # html supporting files
#===============================================================================
# Check for file existance
#===============================================================================
if(! file.exists(proteome_file))
{
stop(paste("Proteome Data file does not exists. Path given: ",proteome_file,sep="",collapse=""));
}
if(! file.exists(transcriptome_file))
{
stop(paste("Transcriptome Data file does not exists. Path given: ",transcriptome_file,sep="",collapse=""));
}
#===============================================================================
# Load library
#===============================================================================
options(warn=-1);
suppressPackageStartupMessages(library(dplyr));
suppressPackageStartupMessages(library(data.table));
suppressPackageStartupMessages(library(gplots));
suppressPackageStartupMessages(library(ggplot2));
suppressPackageStartupMessages(library(ggfortify));
#suppressPackageStartupMessages(library(plotly));
#===============================================================================
# Select mode and parse experiment design file
#===============================================================================
if(mode=="multiple")
{
expDesign = fread(sampleinfo_file, header = FALSE, stringsAsFactors = FALSE, sep="\t") %>% data.frame();
expDesign_cc = expDesign[1:2,];
sampleinfo_df = expDesign[3:nrow(expDesign),];
rownames(sampleinfo_df)=1:nrow(sampleinfo_df);
colnames(sampleinfo_df) = c("Sample","Group");
condition_cols = sampleinfo_df[which(sampleinfo_df[,2]==expDesign_cc[which(expDesign_cc[,1]=="case"),2]),1];
condition_g_name = "case";
control_cols = sampleinfo_df[which(sampleinfo_df[,2]==expDesign_cc[which(expDesign_cc[,1]=="control"),2]),1];
control_g_name = "control";
sampleinfo_df[which(sampleinfo_df[,2]==expDesign_cc[which(expDesign_cc[,1]=="case"),2]),2] = "case";
sampleinfo_df[which(sampleinfo_df[,2]==expDesign_cc[which(expDesign_cc[,1]=="control"),2]),2] = "control";
sampleinfo_df_orig = sampleinfo_df;
}
if(mode=="logfold")
{
sampleinfo_df = data.frame("Sample"= c("LogFold"), "Group"=c("Fold_Change"))
}
#===============================================================================
# Parse Transcriptome data
#===============================================================================
TE_df_orig = fread(transcriptome_file, sep="\t", stringsAsFactor=F, header=T) %>% data.frame();
if(mode=="multiple")
{
TE_df = TE_df_orig[,c(colnames(TE_df_orig)[1],condition_cols,control_cols)];
}
if(mode=="logfold")
{
TE_df = TE_df_orig;
colnames(TE_df) = c("Genes", "LogFold");
}
#===============================================================================
# Parse Proteome data
#===============================================================================
PE_df_orig = fread(proteome_file, sep="\t", stringsAsFactor=F, header=T) %>% data.frame();
if(mode=="multiple")
{
PE_df = PE_df_orig[,c(colnames(PE_df_orig)[1],condition_cols,control_cols)];
}
if(mode=="logfold")
{
PE_df = PE_df_orig;
colnames(PE_df) = c("Genes", "LogFold");
}
#=============================================================================================================
# Create directory structures and then set the working directory to output directory
#=============================================================================================================
if(! file.exists(outdir))
{
dir.create(outdir);
}
#===============================================================================
# Write initial data summary in html outfile
#===============================================================================
cat("<html><head></head><body>\n", file = htmloutfile);
cat("<h1><u>QuanTP: Association between abundance ratios of transcript and protein</u></h1><hr/>\n",
"<font><h3>Input data summary</h3></font>\n",
"<ul>\n",
"<li>Abbreviations used: PE (Proteome data) and TE (Transcriptome data)","</li><br>\n",
"<li>Input Proteome data dimension (Row Column): ", dim(PE_df)[1]," x ", dim(PE_df)[2],"</li>\n",
"<li>Input Transcriptome data dimension (Row Column): ", dim(TE_df)[1]," x ", dim(TE_df)[2],"</li></ul><hr/>\n",
file = htmloutfile, append = TRUE);
cat("<h3 id=table_of_content>Table of Contents:</h3>\n",
"<ul>\n",
"<li><a href=#sample_dist>Sample distribution</a></li>\n",
"<li><a href=#corr_data>Correlation</a></li>\n",
"<li><a href=#regression_data>Regression analysis</a></li>\n",
"<li><a href=#inf_obs>Influential observations</a></li>\n",
"<li><a href=#cluster_data>Cluster analysis</a></li></ul><hr/>\n",
file = htmloutfile, append = TRUE);
#===============================================================================
# Find common samples
#===============================================================================
common_samples = intersect(sampleinfo_df[,1], colnames(TE_df)[-1]) %>% intersect(., colnames(PE_df)[-1]);
if(length(common_samples)==0)
{
stop("No common samples found ");
cat("<b>Please check your experiment design file. Sample names (column names) in the Transcriptome and the Proteome data do not match. </b>\n",file = htmloutfile, append = TRUE);
}
#===============================================================================
# Create subsets based on common samples
#===============================================================================
TE_df = select(TE_df, 1, common_samples);
PE_df = select(PE_df, 1, common_samples);
sampleinfo_df = filter(sampleinfo_df, Sample %in% common_samples);
rownames(sampleinfo_df) = sampleinfo_df[,1];
#===============================================================================
# Check for number of rows similarity
#===============================================================================
if(nrow(TE_df) != nrow(PE_df))
{
stop("Number of rows in Transcriptome and Proteome data are not same i.e. they are not paired");
cat("<b>The correlation analysis expects paired TE and PE data i.e. (i)th gene/transcript of TE file should correspond to (i)th protein of PE file. In the current input provided there is mismatch in terms of number of rows of TE and PE file. Please make sure you provide paired data.</b>\n",file = htmloutfile, append = TRUE);
}
#===============================================================================
# Number of groups
#===============================================================================
ngrps = unique(sampleinfo_df[,2]) %>% length();
grps = unique(sampleinfo_df[,2]);
names(grps) = grps;
#===============================================================================
# Change column1 name
#===============================================================================
colnames(TE_df)[1] = "Gene";
colnames(PE_df)[1] = "Protein";
#===============================================================================
# Treat missing values
#===============================================================================
TE_nacount = sum(is.na(TE_df));
PE_nacount = sum(is.na(PE_df));
TE_df[is.na(TE_df)] = 0;
PE_df[is.na(PE_df)] = 0;
#===============================================================================
# Decide based on analysis mode
#===============================================================================
if(mode=="logfold")
{
cat('<h2 id="sample_dist"><font color=#ff0000>SAMPLE DISTRIBUTION</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE Boxplot
outplot = paste(outdir,"/Box_TE.png",sep="",collape="");
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; ">\n',
'<tr bgcolor="#7a0019"><th><font color=#ffcc33>Boxplot: Transcriptome data</font></th><th><font color=#ffcc33>Boxplot: Proteome data</font></th></tr>\n',
"<tr><td align=center>", '<img src="Box_TE.png" width=500 height=500></td>\n', file = htmloutfile, append = TRUE);
multisample_boxplot(TE_df, sampleinfo_df, outplot, "Yes", "Samples", "Transcript Abundance data");
# PE Boxplot
outplot = paste(outdir,"/Box_PE.png",sep="",collape="");
cat("<td align=center>", '<img src="Box_PE.png" width=500 height=500></td></tr></table>\n', file = htmloutfile, append = TRUE);
multisample_boxplot(PE_df, sampleinfo_df, outplot, "Yes", "Samples", "Protein Abundance data");
cat('<hr/><h2 id="corr_data"><font color=#ff0000>CORRELATION</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE PE scatter
outplot = paste(outdir,"/TE_PE_scatter.png",sep="",collape="");
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Scatter plot between Proteome and Transcriptome Abundance</font></th></tr>\n', file = htmloutfile, append = TRUE);
cat("<tr><td align=center>", '<img src="TE_PE_scatter.png" width=800 height=800></td></tr>\n', file = htmloutfile, append = TRUE);
PE_TE_data = data.frame(PE_df, TE_df);
colnames(PE_TE_data) = c("PE_ID","PE_abundance","TE_ID","TE_abundance");
singlesample_scatter(PE_TE_data, outplot);
# TE PE Cor
cat("<tr><td align=center>", file = htmloutfile, append = TRUE);
singlesample_cor(PE_TE_data, htmloutfile, append=TRUE);
cat('<font color="red">*Note that <u>correlation</u> is <u>sensitive to outliers</u> in the data. So it is important to analyze outliers/influential observations in the data.<br> Below we use <u>Cook\'s distance based approach</u> to identify such influential observations.</font>\n',
file = htmloutfile, append = TRUE);
cat('</td></table>',
file = htmloutfile, append = TRUE);
cat('<hr/><h2 id="regression_data"><font color=#ff0000>REGRESSION ANALYSIS</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE PE Regression
singlesample_regression(PE_TE_data,htmloutfile, append=TRUE);
cat('<hr/><h2 id="cluster_data"><font color=#ff0000>CLUSTER ANALYSIS</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE PE Heatmap
singlesample_heatmap(PE_TE_data, htmloutfile, hm_nclust);
# TE PE Clustering (kmeans)
singlesample_kmeans(PE_TE_data, htmloutfile, nclust=as.numeric(numCluster))
}else{
if(mode=="multiple")
{
cat('<h2 id="sample_dist"><font color=#ff0000>SAMPLE DISTRIBUTION</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE Boxplot
outplot = paste(outdir,"/Box_TE_all_rep.png",sep="",collape="");
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; ">\n',
'<tr bgcolor="#7a0019"><th><font color=#ffcc33>Boxplot: Transcriptome data</font></th><th><font color=#ffcc33>Boxplot: Proteome data</font></th></tr>\n',
"<tr><td align=center>", '<img src="Box_TE_all_rep.png" width=500 height=500></td>\n', file = htmloutfile, append = TRUE);
temp_df_te_data = data.frame(TE_df[,1], log(TE_df[,2:length(TE_df)]));
colnames(temp_df_te_data) = colnames(TE_df);
multisample_boxplot(temp_df_te_data, sampleinfo_df, outplot, "Yes", "Samples", "Transcript Abundance (log)");
# PE Boxplot
outplot = paste(outdir,"/Box_PE_all_rep.png",sep="",collape="");
cat("<td align=center>", '<img src="Box_PE_all_rep.png" width=500 height=500></td></tr></table>\n', file = htmloutfile, append = TRUE);
temp_df_pe_data = data.frame(PE_df[,1], log(PE_df[,2:length(PE_df)]));
colnames(temp_df_pe_data) = colnames(PE_df);
multisample_boxplot(temp_df_pe_data, sampleinfo_df, outplot, "Yes", "Samples", "Protein Abundance (log)");
# Calc TE PCA
outplot = paste(outdir,"/PCA_TE_all_rep.png",sep="",collape="");
multisample_PCA(TE_df, sampleinfo_df, outplot);
# Calc PE PCA
outplot = paste(outdir,"/PCA_PE_all_rep.png",sep="",collape="");
multisample_PCA(PE_df, sampleinfo_df, outplot);
# Replicate mode
templist = mergeReplicates(TE_df,PE_df, sampleinfo_df, method);
TE_df = templist$TE_df_merged;
PE_df = templist$PE_df_merged;
sampleinfo_df = templist$sampleinfo_df_merged;
rownames(sampleinfo_df) = sampleinfo_df[,1];
# TE Boxplot
outplot = paste(outdir,"/Box_TE_rep.png",sep="",collape="");
cat('<br><font color="#ff0000"><h3>Sample wise distribution (Box plot) after using ',method,' on replicates </h3></font><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Boxplot: Transcriptome data</font></th><th><font color=#ffcc33>Boxplot: Proteome data</font></th></tr>\n',
"<tr><td align=center>", '<img src="Box_TE_rep.png" width=500 height=500></td>\n', file = htmloutfile, append = TRUE);
temp_df_te_data = data.frame(TE_df[,1], log(TE_df[,2:length(TE_df)]));
colnames(temp_df_te_data) = colnames(TE_df);
multisample_boxplot(temp_df_te_data, sampleinfo_df, outplot, "No", "Sample Groups", "Mean Transcript Abundance (log)");
# PE Boxplot
outplot = paste(outdir,"/Box_PE_rep.png",sep="",collape="");
cat("<td align=center>", '<img src="Box_PE_rep.png" width=500 height=500></td></tr></table>\n', file = htmloutfile, append = TRUE);
temp_df_pe_data = data.frame(PE_df[,1], log(PE_df[,2:length(PE_df)]));
colnames(temp_df_pe_data) = colnames(PE_df);
multisample_boxplot(temp_df_pe_data, sampleinfo_df, outplot, "No", "Sample Groups", "Mean Protein Abundance (log)");
#===============================================================================
# Calculating log fold change and running the "single" code part
#===============================================================================
TE_df = data.frame("Genes"=TE_df[,1], "LogFold"=apply(TE_df[,c(which(colnames(TE_df)==condition_g_name),which(colnames(TE_df)==control_g_name))],1,function(x) log(x[1]/x[2],base=2)));
PE_df = data.frame("Genes"=PE_df[,1], "LogFold"=apply(PE_df[,c(which(colnames(PE_df)==condition_g_name),which(colnames(PE_df)==control_g_name))],1,function(x) log(x[1]/x[2],base=2)));
#===============================================================================
# Treat missing values
#===============================================================================
TE_df[is.infinite(TE_df[,2]),2] = NA;
PE_df[is.infinite(PE_df[,2]),2] = NA;
TE_df[is.na(TE_df)] = 0;
PE_df[is.na(PE_df)] = 0;
sampleinfo_df = data.frame("Sample"= c("LogFold"), "Group"=c("Fold_Change"))
#===============================================================================
# Find common samples
#===============================================================================
common_samples = intersect(sampleinfo_df[,1], colnames(TE_df)[-1]) %>% intersect(., colnames(PE_df)[-1]);
TE_df = select(TE_df, 1, common_samples);
PE_df = select(PE_df, 1, common_samples);
sampleinfo_df = filter(sampleinfo_df, Sample %in% common_samples);
rownames(sampleinfo_df) = sampleinfo_df[,1];
# TE Boxplot
outplot = paste(outdir,"/Box_TE.png",sep="",collape="");
cat('<br><font color="#ff0000"><h3>Distribution (Box plot) of log fold change </h3></font>', file = htmloutfile, append = TRUE);
cat('<table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Boxplot: Transcriptome data</font></th><th><font color=#ffcc33>Boxplot: Proteome data</font></th></tr>\n',
"<tr><td align=center>", '<img src="Box_TE.png" width=500 height=500></td>\n', file = htmloutfile, append = TRUE);
multisample_boxplot(TE_df, sampleinfo_df, outplot, "Yes", "Sample (log2(case/control))", "Transcript Abundance fold-change (log2)");
# PE Boxplot
outplot = paste(outdir,"/Box_PE.png",sep="",collape="");
cat("<td align=center>", '<img src="Box_PE.png" width=500 height=500></td></tr></table>\n', file = htmloutfile, append = TRUE);
multisample_boxplot(PE_df, sampleinfo_df, outplot, "Yes", "Sample (log2(case/control))", "Protein Abundance fold-change(log2)");
# Log Fold Data
perform_Test_Volcano(TE_df_orig,PE_df_orig,TE_df, PE_df,sampleinfo_df_orig,method,correction_method,volc_with)
# Print PCA
cat('<br><br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>PCA plot: Transcriptome data</font></th><th><font color=#ffcc33>PCA plot: Proteome data</font></th></tr>\n',
"<tr><td align=center>", '<img src="PCA_TE_all_rep.png" width=500 height=500></td>\n',
"<td align=center>", '<img src="PCA_PE_all_rep.png" width=500 height=500></td></tr></table>\n',
file = htmloutfile, append = TRUE);
cat('<hr/><h2 id="corr_data"><font color=#ff0000>CORRELATION</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE PE scatter
outplot = paste(outdir,"/TE_PE_scatter.png",sep="",collape="");
cat('<br><table border=1 cellspacing=0 cellpadding=5 style="table-layout:auto; "> <tr bgcolor="#7a0019"><th><font color=#ffcc33>Scatter plot between Proteome and Transcriptome Abundance</font></th></tr>\n', file = htmloutfile, append = TRUE);
cat("<tr><td align=center>", '<img src="TE_PE_scatter.png" width=800 height=800></td>\n', file = htmloutfile, append = TRUE);
PE_TE_data = data.frame(PE_df, TE_df);
colnames(PE_TE_data) = c("PE_ID","PE_abundance","TE_ID","TE_abundance");
singlesample_scatter(PE_TE_data, outplot);
# TE PE Cor
cat("<tr><td align=center>\n", file = htmloutfile, append = TRUE);
singlesample_cor(PE_TE_data, htmloutfile, append=TRUE);
cat('<font color="red">*Note that <u>correlation</u> is <u>sensitive to outliers</u> in the data. So it is important to analyze outliers/influential observations in the data.<br> Below we use <u>Cook\'s distance based approach</u> to identify such influential observations.</font>\n',
file = htmloutfile, append = TRUE);
cat('</td></table>',
file = htmloutfile, append = TRUE);
cat('<hr/><h2 id="regression_data"><font color=#ff0000>REGRESSION ANALYSIS</font></h2>\n',
file = htmloutfile, append = TRUE);
# TE PE Regression
singlesample_regression(PE_TE_data,htmloutfile, append=TRUE);
cat('<hr/><h2 id="cluster_data"><font color=#ff0000>CLUSTER ANALYSIS</font></h2>\n',
file = htmloutfile, append = TRUE);
#TE PE Heatmap
singlesample_heatmap(PE_TE_data, htmloutfile, hm_nclust);
#TE PE Clustering (kmeans)
singlesample_kmeans(PE_TE_data, htmloutfile, nclust=as.numeric(numCluster))
}
}
NaNs producedNaNs produced
cat("<h3>Go To:</h3>\n",
"<ul>\n",
"<li><a href=#sample_dist>Sample distribution</a></li>\n",
"<li><a href=#corr_data>Correlation</a></li>\n",
"<li><a href=#regression_data>Regression analysis</a></li>\n",
"<li><a href=#inf_obs>Influential observations</a></li>\n",
"<li><a href=#cluster_data>Cluster analysis</a></li></ul>\n",
"<br><a href=#>TOP</a>",
file = htmloutfile, append = TRUE);
cat("</body></html>\n", file = htmloutfile, append = TRUE);
library(htmlwidgets)
library(plotly)
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpgYGB7cn0KCiMqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKIyBGdW5jdGlvbnM6IFN0YXJ0CiMqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgUENBCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cm11bHRpc2FtcGxlX1BDQSA9IGZ1bmN0aW9uKGRmLCBzYW1wbGVpbmZvX2RmLCBvdXRmaWxlKQp7CiAgdGVtcGRmID0gZGZbLC0xXTsKICB0ZW1wY29sID0gY29sbmFtZXModGVtcGRmKTsKICB0ZW1wZ3JwID0gc2FtcGxlaW5mb19kZlt0ZW1wY29sLDJdOwogIHRlbXBkZiA9IHQodGVtcGRmKSAlPiUgYXMuZGF0YS5mcmFtZSgpOwogIHRlbXBkZltpcy5uYSh0ZW1wZGYpXSA9IDA7CiAgdGVtcGRmJEdyb3VwID0gdGVtcGdycDsKICBwbmcob3V0ZmlsZSwgd2lkdGggPSA2LCBoZWlnaHQgPSA2LCB1bml0cyA9ICdpbicsIHJlcz0zMDApOwogICMgYml0bWFwKG91dGZpbGUsICJwbmcxNm0iKTsKICBnID0gYXV0b3Bsb3QocHJjb21wKHNlbGVjdCh0ZW1wZGYsIC1Hcm91cCkpLCBkYXRhID0gdGVtcGRmLCBjb2xvdXIgPSAnR3JvdXAnLCBzaXplPTMpOwogIHBsb3QoZyk7CiAgZGV2Lm9mZigpOwp9CgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFJlZ3Jlc3Npb24gYW5kIENvb2sncyBkaXN0YW5jZQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpzaW5nbGVzYW1wbGVfcmVncmVzc2lvbiA9IGZ1bmN0aW9uKFBFX1RFX2RhdGEsaHRtbG91dGZpbGUsIGFwcGVuZD1UUlVFKQp7CiAgcm93bmFtZXMoUEVfVEVfZGF0YSkgPSBQRV9URV9kYXRhJFBFX0lEOwogIHJlZ21vZGVsID0gbG0oUEVfYWJ1bmRhbmNlflRFX2FidW5kYW5jZSwgZGF0YT1QRV9URV9kYXRhKTsKICByZWdtb2RlbF9zdW1tYXJ5ID0gc3VtbWFyeShyZWdtb2RlbCk7CiAgCiAgY2F0KCI8Zm9udD48aDM+TGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgZml0IGJldHdlZW4gUHJvdGVvbWUgYW5kIFRyYW5zY3JpcHRvbWUgZGF0YTwvaDM+PC9mb250PlxuIiwKICAgICI8cD5Bc3N1bWluZyBhIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBQcm90ZW9tZSBhbmQgVHJhbnNjcmlwdG9tZSBkYXRhLCB3ZSBoZXJlIGZpdCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLjwvcD5cbiIsCiAgICAnPHRhYmxlICBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+UGFyYW1ldGVyPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+VmFsdWU8L2ZvbnQ+PC90aD48L3RyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgY2F0KCI8dHI+PHRkPkZvcm11bGE8L3RkPjx0ZD4iLCJQRV9hYnVuZGFuY2V+VEVfYWJ1bmRhbmNlIiwiPC90ZD48L3RyPlxuIiwKICAgICI8dHI+PHRkIGNvbHNwYW49JzInIGFsaWduPSdjZW50ZXInPiA8Yj5Db2VmZmljaWVudHM8L2I+PC90ZD4iLCI8L3RyPlxuIiwKICAgICI8dHI+PHRkPiIsbmFtZXMocmVnbW9kZWwkY29lZmZpY2llbnRzWzFdKSwiPC90ZD48dGQ+IixyZWdtb2RlbCRjb2VmZmljaWVudHNbMV0sIiAoUHZhbHVlOiIsIHJlZ21vZGVsX3N1bW1hcnkkY29lZmZpY2llbnRzWzEsNF0sIikiLCI8L3RkPjwvdHI+XG4iLAogICAgIjx0cj48dGQ+IixuYW1lcyhyZWdtb2RlbCRjb2VmZmljaWVudHNbMl0pLCI8L3RkPjx0ZD4iLHJlZ21vZGVsJGNvZWZmaWNpZW50c1syXSwiIChQdmFsdWU6IiwgcmVnbW9kZWxfc3VtbWFyeSRjb2VmZmljaWVudHNbMiw0XSwiKSIsIjwvdGQ+PC90cj5cbiIsCiAgICAiPHRyPjx0ZCBjb2xzcGFuPScyJyBhbGlnbj0nY2VudGVyJz4gPGI+TW9kZWwgcGFyYW1ldGVyczwvYj48L3RkPiIsIjwvdHI+XG4iLAogICAgIjx0cj48dGQ+UmVzaWR1YWwgc3RhbmRhcmQgZXJyb3I8L3RkPjx0ZD4iLHJlZ21vZGVsX3N1bW1hcnkkc2lnbWEsIiAoIixyZWdtb2RlbF9zdW1tYXJ5JGRmWzJdLCIgZGVncmVlIG9mIGZyZWVkb20pPC90ZD48L3RyPlxuIiwKICAgICI8dHI+PHRkPkYtc3RhdGlzdGljPC90ZD48dGQ+IixyZWdtb2RlbF9zdW1tYXJ5JGZzdGF0aXN0aWNbMV0sIiAoIG9uICIscmVnbW9kZWxfc3VtbWFyeSRmc3RhdGlzdGljWzJdLCIgYW5kICAiLHJlZ21vZGVsX3N1bW1hcnkkZnN0YXRpc3RpY1szXSwiIGRlZ3JlZSBvZiBmcmVlZG9tKTwvdGQ+PC90cj5cbiIsCiAgICAiPHRyPjx0ZD5SLXNxdWFyZWQ8L3RkPjx0ZD4iLHJlZ21vZGVsX3N1bW1hcnkkci5zcXVhcmVkLCI8L3RkPjwvdHI+XG4iLAogICAgIjx0cj48dGQ+QWRqdXN0ZWQgUi1zcXVhcmVkPC90ZD48dGQ+IixyZWdtb2RlbF9zdW1tYXJ5JGFkai5yLnNxdWFyZWQsIjwvdGQ+PC90cj5cbiIsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGNhdCgiPC90YWJsZT5cbiIsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgY2F0KAogICAgIjxmb250IGNvbG9yPScjZmYwMDAwJz48aDM+UmVncmVzc2lvbiBhbmQgZGlhZ25vc3RpY3MgcGxvdHM8L2gzPjwvZm9udD5cbiIsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9QRV9URV9sbV8xLnBuZyIsc2VwPSIiLGNvbGxhcHNlPSIiKTsKICBwbmcob3V0cGxvdCwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTAsIHVuaXRzID0gJ2luJyxyZXM9MzAwKTsKICAjIGJpdG1hcChvdXRwbG90LCAicG5nMTZtIik7CiAgcGFyKG1mcm93PWMoMSwxKSk7CiAgcGxvdChyZWdtb2RlbCwgMSwgY2V4LmxhYj0xLjUpOwogIGRldi5vZmYoKTsKICAKICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvUEVfVEVfbG1fMi5wbmciLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgcG5nKG91dHBsb3Qsd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTAsIHVuaXRzID0gJ2luJywgcmVzPTMwMCk7CiAgIyBiaXRtYXAob3V0cGxvdCwgInBuZzE2bSIpOwogIHBhcihtZnJvdz1jKDEsMSkpOwogIHBsb3QocmVnbW9kZWwsIDIsIGNleC5sYWI9MS41KTsKICBkZXYub2ZmKCk7CiAgCiAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL1BFX1RFX2xtXzUucG5nIixzZXA9IiIsY29sbGFwc2U9IiIpOwogIHBuZyhvdXRwbG90LCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCwgdW5pdHMgPSAnaW4nLHJlcz0zMDApOwogICMgYml0bWFwKG91dHBsb3QsICJwbmcxNm0iKTsKICBwYXIobWZyb3c9YygxLDEpKTsKICBwbG90KHJlZ21vZGVsLCA1LCBjZXgubGFiPTEuNSk7CiAgZGV2Lm9mZigpOwogIAogIGNhdCgnPHRhYmxlIGJvcmRlcj0xIGNlbGxzcGFjaW5nPTAgY2VsbHBhZGRpbmc9NSBzdHlsZT0idGFibGUtbGF5b3V0OmF1dG87ICI+JywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKICAgIGNhdCgKICAgICc8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPicsICI8Zm9udCBjb2xvcj0nI2ZmY2MzMyc+PGg0PjEpIDx1PlJlc2lkdWFscyB2cyBGaXR0ZWQgcGxvdDwvaDQ+PC9mb250PjwvdT48L3RoPlxuIiwKICAgICc8dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz48aDQ+MikgPHU+Tm9ybWFsIFEtUSBwbG90IG9mIHJlc2lkdWFsczwvaDQ+PC9mb250PjwvdT48L3RoPjwvdHI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKICBjYXQoCiAgICAnPHRyPjx0ZCBhbGlnbj1jZW50ZXI+PGltZyBzcmM9IlBFX1RFX2xtXzEucG5nIiB3aWR0aD02MDAgaGVpZ2h0PTYwMD48L3RkPjx0ZCBhbGlnbj1jZW50ZXI+PGltZyBzcmM9IlBFX1RFX2xtXzIucG5nIiB3aWR0aD02MDAgaGVpZ2h0PTYwMD48L3RkPjwvdHI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKICBjYXQoCiAgICAnPHRyPjx0ZCBhbGlnbj1jZW50ZXI+VGhpcyBwbG90IGNoZWNrcyBmb3IgbGluZWFyIHJlbGF0aW9uc2hpcCBhc3N1bXB0aW9ucy48YnI+SWYgYSBob3Jpem9udGFsIGxpbmUgaXMgb2JzZXJ2ZWQgd2l0aG91dCBhbnkgZGlzdGluY3QgcGF0dGVybnMsIGl0IGluZGljYXRlcyBhIGxpbmVhciByZWxhdGlvbnNoaXAuPC90ZD5cbicsCiAgICAnPHRkIGFsaWduPWNlbnRlcj5UaGlzIHBsb3QgY2hlY2tzIHdoZXRoZXIgcmVzaWR1YWxzIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZCBvciBub3QuPGJyPkl0IGlzIGdvb2QgaWYgdGhlIHJlc2lkdWFscyBwb2ludHMgZm9sbG93IHRoZSBzdHJhaWdodCBkYXNoZWQgbGluZSBpLmUuLCBkbyBub3QgZGV2aWF0ZSBtdWNoIGZyb20gZGFzaGVkIGxpbmUuPC90ZD48L3RyPjwvdGFibGU+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgCiAgICAjQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAogICAgIyBSZXNpZHVhbHMgZGF0YQogICAgI0BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAKICAgIHJlc19hbGwgPSByZWdtb2RlbCRyZXNpZHVhbHM7CiAgICByZXNfbWVhbiA9IG1lYW4ocmVzX2FsbCk7CiAgICByZXNfc2QgPSBzZChyZXNfYWxsKTsKICAgIHJlc19kaWZmID0gKHJlc19hbGwtcmVzX21lYW4pOwogICAgcmVzX3pzY29yZSA9IHJlc19kaWZmL3Jlc19zZDsKICAgICMgcmVzX291dGxpZXJzID0gcmVzX2FsbFt3aGljaCgocmVzX3pzY29yZSA+IDIpfChyZXNfenNjb3JlIDwgLTIpKV0KICAgIAogICAgCiAgICB0ZW1waW5kID0gd2hpY2goKHJlc196c2NvcmUgPiAyKXwocmVzX3pzY29yZSA8IC0yKSk7CiAgICByZXNfUEVfVEVfZGF0YV9ub19vdXRsaWVyID0gUEVfVEVfZGF0YVstdGVtcGluZCxdOwogICAgcmVzX1BFX1RFX2RhdGFfbm9fb3V0bGllciRyZXNpZHVhbHMgPSByZXNfYWxsWy10ZW1waW5kXTsKICAgIHJlc19QRV9URV9kYXRhX291dGxpZXIgPSBQRV9URV9kYXRhW3RlbXBpbmQsXTsKICAgIHJlc19QRV9URV9kYXRhX291dGxpZXIkcmVzaWR1YWxzID0gcmVzX2FsbFt0ZW1waW5kXTsKICAKICAjIFNhdmUgdGhlIGNvbXBsZXRlIHRhYmxlIGZvciBkb3dubG9hZCAoaW5mbHVlbnRpYWxfb2JzZXJ2YXRpb25zKQogICAgdGVtcF9vdXRsaWVyX2RhdGEgPSBkYXRhLmZyYW1lKHJlc19QRV9URV9kYXRhX291dGxpZXIkUEVfSUQsIHJlc19QRV9URV9kYXRhX291dGxpZXIkVEVfYWJ1bmRhbmNlLCByZXNfUEVfVEVfZGF0YV9vdXRsaWVyJFBFX2FidW5kYW5jZSwgcmVzX1BFX1RFX2RhdGFfb3V0bGllciRyZXNpZHVhbHMpCiAgICBjb2xuYW1lcyh0ZW1wX291dGxpZXJfZGF0YSkgPSBjKCJHZW5lIiwgIlRyYW5zY3JpcHQgYWJ1bmRhbmNlIiwgIlByb3RlaW4gYWJ1bmRhbmNlIiwgIlJlc2lkdWFsIHZhbHVlIikKICAgIG91dGRhdGFmaWxlID0gcGFzdGUob3V0ZGlyLCIvUEVfVEVfb3V0bGllcnNfcmVzaWR1YWxzLnR4dCIsIHNlcD0iIiwgY29sbGFwc2U9IiIpOwogICAgd3JpdGUudGFibGUodGVtcF9vdXRsaWVyX2RhdGEsIGZpbGU9b3V0ZGF0YWZpbGUsIHJvdy5uYW1lcz1GLCBzZXA9Ilx0IiwgcXVvdGU9Rik7CiAgCiAgCiAgIyBTYXZlIHRoZSBjb21wbGV0ZSB0YWJsZSBmb3IgZG93bmxvYWQgKG5vbiBpbmZsdWVudGlhbF9vYnNlcnZhdGlvbnMpCiAgICB0ZW1wX2FsbF9kYXRhID0gZGF0YS5mcmFtZShQRV9URV9kYXRhJFBFX0lELCBQRV9URV9kYXRhJFRFX2FidW5kYW5jZSwgUEVfVEVfZGF0YSRQRV9hYnVuZGFuY2UsIHJlc19hbGwpCiAgICBjb2xuYW1lcyh0ZW1wX2FsbF9kYXRhKSA9IGMoIkdlbmUiLCAiVHJhbnNjcmlwdCBhYnVuZGFuY2UiLCAiUHJvdGVpbiBhYnVuZGFuY2UiLCAiUmVzaWR1YWwgdmFsdWUiKQogICAgb3V0ZGF0YWZpbGUgPSBwYXN0ZShvdXRkaXIsIi9QRV9URV9hYnVuZGFuY2VfcmVzaWR1YWxzLnR4dCIsIHNlcD0iIiwgY29sbGFwc2U9IiIpOwogICAgd3JpdGUudGFibGUodGVtcF9hbGxfZGF0YSwgZmlsZT1vdXRkYXRhZmlsZSwgcm93Lm5hbWVzPUYsIHNlcD0iXHQiLCBxdW90ZT1GKTsKICAKICAKICBjYXQoJzxicj48aDIgaWQ9ImluZl9vYnMiPjxmb250IGNvbG9yPSNmZjAwMDA+T3V0bGllcnMgYmFzZWQgb24gdGhlIHJlc2lkdWFscyBmcm9tIHJlZ3Jlc3Npb24gYW5hbHlzaXM8L2ZvbnQ+PC9oMj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIGNhdCgnPHRhYmxlIGJvcmRlcj0xIGNlbGxzcGFjaW5nPTAgY2VsbHBhZGRpbmc9NSBzdHlsZT0idGFibGUtbGF5b3V0OmF1dG87ICI+XG4nLAogICc8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoIGNvbHNwYW49Mj48Zm9udCBjb2xvcj0jZmZjYzMzPlJlc2lkdWFscyBmcm9tIFJlZ3Jlc3Npb248L2ZvbnQ+PC90aD48L3RyPlxuJywKICAgJzx0ciBiZ2NvbG9yPSIjN2EwMDE5Ij48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5QYXJhbWV0ZXI8L2ZvbnQ+PC90aD48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5WYWx1ZTwvZm9udD48L3RoPjwvdHI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogIGNhdCgiPHRyPjx0ZD5NZWFuIFJlc2lkdWFsIHZhbHVlPC90ZD48dGQ+IixyZXNfbWVhbiwiPC90ZD48L3RyPlxuIiwKICAgICI8dHI+PHRkPlN0YW5kYXJkIGRldmlhdGlvbiAoUmVzaWR1YWxzKTwvdGQ+PHRkPiIscmVzX3NkLCI8L3RkPjwvdHI+XG4iLAogICAgJzx0cj48dGQ+VG90YWwgb3V0bGllcnMgKFJlc2lkdWFsIHZhbHVlID4gMiBzdGFuZGFyZCBkZXZpYXRpb24gZnJvbSB0aGUgbWVhbik8L3RkPjx0ZD4nLGxlbmd0aCh0ZW1waW5kKSwnIDxmb250IHNpemU9ND4oPGI+PGEgaHJlZj1QRV9URV9vdXRsaWVyc19yZXNpZHVhbHMudHh0IHRhcmdldD0iX2JsYW5rIj5Eb3dubG9hZCB0aGVzZSAnLGxlbmd0aCh0ZW1waW5kKSwnIGRhdGEgcG9pbnRzIHdpdGggaGlnaCByZXNpZHVhbCB2YWx1ZXMgaGVyZTwvYT48L2I+KTwvZm9udD48L3RkPlxuJywKICAgICc8dHI+PHRkIGNvbHNwYW49MiBhbGlnbj1jZW50ZXI+PGZvbnQgc2l6ZT00Pig8Yj48YSBocmVmPVBFX1RFX2FidW5kYW5jZV9yZXNpZHVhbHMudHh0IHRhcmdldD0iX2JsYW5rIj5Eb3dubG9hZCB0aGUgY29tcGxldGUgcmVzaWR1YWxzIGRhdGEgaGVyZTwvYT48L2I+KTwvZm9udD48L3RkPjwvdGQ+XG4nLAogICAgIjwvdGFibGU+PGJyPjxicj5cbiIsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgCiAgI0BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAKICAgIAogICAgCiAgY2F0KCc8YnI+PGJyPjx0YWJsZSBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPicsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CgogIGNhdCgKICAgICc8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+PGg0PjMpIDx1PlJlc2lkdWFscyB2cyBMZXZlcmFnZSBwbG90PC9oND48L2ZvbnQ+PC91PjwvdGg+PC90cj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGNhdCgKICAgICc8dHI+PHRkIGFsaWduPWNlbnRlcj48aW1nIHNyYz0iUEVfVEVfbG1fNS5wbmciIHdpZHRoPTYwMCBoZWlnaHQ9NjAwPjwvdGQ+PC90cj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGNhdCgKICAgICc8dHI+PHRkIGFsaWduPWNlbnRlcj5UaGlzIHBsb3QgaXMgdXNlZnVsIHRvIGlkZW50aWZ5IGFueSBpbmZsdWVudGlhbCBjYXNlcywgdGhhdCBpcyBvdXRsaWVycyBvciBleHRyZW1lIHZhbHVlcy48YnI+VGhleSBtaWdodCBpbmZsdWVuY2UgdGhlIHJlZ3Jlc3Npb24gcmVzdWx0cyB1cG9uIGluY2x1c2lvbiBvciBleGNsdXNpb24gZnJvbSB0aGUgYW5hbHlzaXMuPC90ZD48L3RyPjwvdGFibGU+PGJyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgCiAgCiAgI15eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eCiAgIyBDb29rJ3MgRGlzdGFuY2UKICAjXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4KICBjYXQoJzxoci8+PGgyIGlkPSJpbmZfb2JzIj48Zm9udCBjb2xvcj0jZmYwMDAwPklORkxVRU5USUFMIE9CU0VSVkFUSU9OUzwvZm9udD48L2gyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgY2F0KAogICAgJzxwPjxiPkNvb2tcJ3MgZGlzdGFuY2U8L2I+IGNvbXB1dGVzIHRoZSBpbmZsdWVuY2Ugb2YgZWFjaCBkYXRhIHBvaW50L29ic2VydmF0aW9uIG9uIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZS4gaS5lLiB0aGlzIG1lYXN1cmVzIGhvdyBtdWNoIHRoZSBvYnNlcnZhdGlvbiBpcyBpbmZsdWVuY2luZyB0aGUgZml0dGVkIHZhbHVlcy48YnI+SW4gZ2VuZXJhbCB1c2UsIHRob3NlIG9ic2VydmF0aW9ucyB0aGF0IGhhdmUgYSA8Yj5Db29rXCdzIGRpc3RhbmNlID4gdGhhbiAnLCBjb29rZGlzdF91cHBlcl9jdXRvZmYsJyB0aW1lcyB0aGUgbWVhbjwvYj4gbWF5IGJlIGNsYXNzaWZpZWQgYXMgPGI+aW5mbHVlbnRpYWwuPC9iPjwvcD5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGNvb2tzZCA8LSBjb29rcy5kaXN0YW5jZShyZWdtb2RlbCk7CiAgCiAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL1BFX1RFX2xtX2Nvb2tzZC5wbmciLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgcG5nKG91dHBsb3QsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwLCB1bml0cyA9ICdpbicsIHJlcz0zMDApOwogICMgYml0bWFwKG91dHBsb3QsICJwbmcxNm0iKTsKICBwYXIobWZyb3c9YygxLDEpKTsKICBwbG90KGNvb2tzZCwgbWFpbj0iSW5mbHVlbnRpYWwgT2JzLiBieSBDb29rXCdzIGRpc3RhbmNlIiwgeWxhYj0iQ29va1wncyBkaXN0YW5jZSIsIHhsYWI9Ik9ic2VydmF0aW9ucyIsIHR5cGU9Im4iKSAgIyBwbG90IGNvb2tzIGRpc3RhbmNlCiAgc2VsX291dGxpZXI9d2hpY2goY29va3NkPj1hcy5udW1lcmljKGNvb2tkaXN0X3VwcGVyX2N1dG9mZikqbWVhbihjb29rc2QsIG5hLnJtPVQpKQogIHNlbF9ub25vdXRsaWVyPXdoaWNoKGNvb2tzZDxhcy5udW1lcmljKGNvb2tkaXN0X3VwcGVyX2N1dG9mZikqbWVhbihjb29rc2QsIG5hLnJtPVQpKQogIHBvaW50cyhzZWxfb3V0bGllciwgY29va3NkW3NlbF9vdXRsaWVyXSxwY2g9IioiLCBjZXg9MiwgY2V4LmxhYj0xLjUsIGNvbD0icmVkIikKICBwb2ludHMoc2VsX25vbm91dGxpZXIsIGNvb2tzZFtzZWxfbm9ub3V0bGllcl0scGNoPSIqIiwgY2V4PTIsIGNleC5sYWI9MS41LCBjb2w9ImJsYWNrIikKICBhYmxpbmUoaCA9IGFzLm51bWVyaWMoY29va2Rpc3RfdXBwZXJfY3V0b2ZmKSptZWFuKGNvb2tzZCwgbmEucm09VCksIGNvbD0icmVkIikgICMgYWRkIGN1dG9mZiBsaW5lCiAgI3RleHQoeD0xOmxlbmd0aChjb29rc2QpKzEsIHk9Y29va3NkLCBsYWJlbHM9aWZlbHNlKGNvb2tzZD5hcy5udW1lcmljKGNvb2tkaXN0X3VwcGVyX2N1dG9mZikqbWVhbihjb29rc2QsIG5hLnJtPVQpLG5hbWVzKGNvb2tzZCksIiIpLCBjb2w9InJlZCIsIHBvcz0yKSAgIyBhZGQgbGFiZWxzCiAgZGV2Lm9mZigpOwogIAogIGNhdCgKICAgICc8aW1nIHNyYz0iUEVfVEVfbG1fY29va3NkLnBuZyIgd2lkdGg9ODAwIGhlaWdodD04MDA+JywKICAgICc8YnI+SW4gdGhlIGFib3ZlIHBsb3QsIG9ic2VydmF0aW9ucyBhYm92ZSByZWQgbGluZSAoJyxjb29rZGlzdF91cHBlcl9jdXRvZmYsJyAqIG1lYW4gQ29va1wncyBkaXN0YW5jZSkgYXJlIGluZmx1ZW50aWFsLiBHZW5lcyB0aGF0IGFyZSBvdXRsaWVycyBjb3VsZCBiZSBpbXBvcnRhbnQuIFRoZXNlIG9ic2VydmF0aW9ucyBpbmZsdWVuY2VzIHRoZSBjb3JyZWxhdGlvbiB2YWx1ZXMgYW5kIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzPGJyPjxicj4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsgICAgCgogIHRlbXBpbmQgPSB3aGljaChjb29rc2Q+YXMubnVtZXJpYyhjb29rZGlzdF91cHBlcl9jdXRvZmYpKm1lYW4oY29va3NkLCBuYS5ybT1UKSk7CiAgUEVfVEVfZGF0YV9ub19vdXRsaWVyID0gUEVfVEVfZGF0YVstdGVtcGluZCxdOwogIFBFX1RFX2RhdGFfbm9fb3V0bGllciRjb29rc2QgPSBjb29rc2RbLXRlbXBpbmRdOwogIFBFX1RFX2RhdGFfb3V0bGllciA9IFBFX1RFX2RhdGFbdGVtcGluZCxdOwogIFBFX1RFX2RhdGFfb3V0bGllciRjb29rc2QgPSBjb29rc2RbdGVtcGluZF07CiAgYSA9IHNvcnQoUEVfVEVfZGF0YV9vdXRsaWVyJGNvb2tzZCwgZGVjcmVhc2luZz1ULCBpbmRleC5yZXR1cm49VCk7CiAgUEVfVEVfZGF0YV9vdXRsaWVyX3NvcnRlZCA9IFBFX1RFX2RhdGFfb3V0bGllclthJGl4LF07CiAgCiAgY2F0KAogICAgJzx0YWJsZSBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+UGFyYW1ldGVyPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+VmFsdWU8L2ZvbnQ+PC90aD48L3RyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgIyBTYXZlIHRoZSBjb21wbGV0ZSB0YWJsZSBmb3IgZG93bmxvYWQgKGluZmx1ZW50aWFsX29ic2VydmF0aW9ucykKICAgIHRlbXBfb3V0bGllcl9kYXRhID0gZGF0YS5mcmFtZShQRV9URV9kYXRhX291dGxpZXIkUEVfSUQsIFBFX1RFX2RhdGFfb3V0bGllciRURV9hYnVuZGFuY2UsIFBFX1RFX2RhdGFfb3V0bGllciRQRV9hYnVuZGFuY2UsIFBFX1RFX2RhdGFfb3V0bGllciRjb29rc2QpCiAgICBjb2xuYW1lcyh0ZW1wX291dGxpZXJfZGF0YSkgPSBjKCJHZW5lIiwgIlRyYW5zY3JpcHQgYWJ1bmRhbmNlIiwgIlByb3RlaW4gYWJ1bmRhbmNlIiwgIkNvb2sncyBkaXN0YW5jZSIpCiAgICBvdXRkYXRhZmlsZSA9IHBhc3RlKG91dGRpciwiL1BFX1RFX2luZmx1ZW50aWFsX29ic2VydmF0aW9uLnR4dCIsIHNlcD0iIiwgY29sbGFwc2U9IiIpOwogICAgd3JpdGUudGFibGUodGVtcF9vdXRsaWVyX2RhdGEsIGZpbGU9b3V0ZGF0YWZpbGUsIHJvdy5uYW1lcz1GLCBzZXA9Ilx0IiwgcXVvdGU9Rik7CiAgCiAgCiAgIyBTYXZlIHRoZSBjb21wbGV0ZSB0YWJsZSBmb3IgZG93bmxvYWQgKG5vbiBpbmZsdWVudGlhbF9vYnNlcnZhdGlvbnMpCiAgICB0ZW1wX25vX291dGxpZXJfZGF0YSA9IGRhdGEuZnJhbWUoUEVfVEVfZGF0YV9ub19vdXRsaWVyJFBFX0lELCBQRV9URV9kYXRhX25vX291dGxpZXIkVEVfYWJ1bmRhbmNlLCBQRV9URV9kYXRhX25vX291dGxpZXIkUEVfYWJ1bmRhbmNlLCBQRV9URV9kYXRhX25vX291dGxpZXIkY29va3NkKQogICAgY29sbmFtZXModGVtcF9ub19vdXRsaWVyX2RhdGEpID0gYygiR2VuZSIsICJUcmFuc2NyaXB0IGFidW5kYW5jZSIsICJQcm90ZWluIGFidW5kYW5jZSIsICJDb29rJ3MgZGlzdGFuY2UiKQogICAgb3V0ZGF0YWZpbGUgPSBwYXN0ZShvdXRkaXIsIi9QRV9URV9ub25faW5mbHVlbnRpYWxfb2JzZXJ2YXRpb24udHh0Iiwgc2VwPSIiLCBjb2xsYXBzZT0iIik7CiAgICB3cml0ZS50YWJsZSh0ZW1wX25vX291dGxpZXJfZGF0YSwgZmlsZT1vdXRkYXRhZmlsZSwgcm93Lm5hbWVzPUYsIHNlcD0iXHQiLCBxdW90ZT1GKTsKICAKICAKICBjYXQoIjx0cj48dGQ+TWVhbiBDb29rXCdzIGRpc3RhbmNlPC90ZD48dGQ+IixtZWFuKGNvb2tzZCwgbmEucm09VCksIjwvdGQ+PC90cj5cbiIsCiAgICAiPHRyPjx0ZD5Ub3RhbCBpbmZsdWVudGlhbCBvYnNlcnZhdGlvbnMgKENvb2tcJ3MgZGlzdGFuY2UgPiAiLGNvb2tkaXN0X3VwcGVyX2N1dG9mZiwiICogbWVhbiBDb29rXCdzIGRpc3RhbmNlKTwvdGQ+PHRkPiIsbGVuZ3RoKHRlbXBpbmQpLCI8L3RkPlxuIiwKICAgIAogICAgIjx0cj48dGQ+T2JzZXJ2YXRpb25zIHdpdGggQ29va1wncyBkaXN0YW5jZSA8ICIsY29va2Rpc3RfdXBwZXJfY3V0b2ZmLCIgKiBtZWFuIENvb2tcJ3MgZGlzdGFuY2U8L3RkPjx0ZD4iLGxlbmd0aCh3aGljaChjb29rc2Q8YXMubnVtZXJpYyhjb29rZGlzdF91cHBlcl9jdXRvZmYpKm1lYW4oY29va3NkLCBuYS5ybT1UKSkpLCI8L3RkPlxuIiwKICAgICI8L3RhYmxlPjxicj48YnI+XG4iLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgCiAgICAjQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAogICAgIyBTY2F0dGVyIHBsb3QgYWZ0ZXIgcmVtb3ZhbCBvZiBpbmZsdWVudGlhbCBwb2ludHMKICAgICNAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBACiAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvQWJ1bmRhbmNlUGxvdF9zY2F0dGVyX3dpdGhvdXRfb3V0bGllcnMucG5nIixzZXA9IiIsY29sbGFwc2U9IiIpOwogICAgbWluX2xpbSA9IG1pbihjKFBFX1RFX2RhdGEkUEVfYWJ1bmRhbmNlLFBFX1RFX2RhdGEkVEVfYWJ1bmRhbmNlKSk7CiAgICBtYXhfbGltID0gbWF4KGMoUEVfVEVfZGF0YSRQRV9hYnVuZGFuY2UsUEVfVEVfZGF0YSRURV9hYnVuZGFuY2UpKTsKICAgIHBuZyhvdXRwbG90LCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCwgdW5pdHMgPSAnaW4nLCByZXM9MzAwKTsKICAgICMgYml0bWFwKG91dHBsb3QsInBuZzE2bSIpOwogICAgZyA9IGdncGxvdChQRV9URV9kYXRhX25vX291dGxpZXIsIGFlcyh4PVRFX2FidW5kYW5jZSwgeT1QRV9hYnVuZGFuY2UpKStnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aCgpICsgeGxhYigiVHJhbnNjcmlwdCBhYnVuZGFuY2UgbG9nIGZvbGQtY2hhbmdlIikgKyB5bGFiKCJQcm90ZWluIGFidW5kYW5jZSBsb2cgZm9sZC1jaGFuZ2UiKSArIHhsaW0obWluX2xpbSxtYXhfbGltKSArIHlsaW0obWluX2xpbSxtYXhfbGltKTsKICAgIHN1cHByZXNzTWVzc2FnZXMocGxvdChnKSk7CiAgICBkZXYub2ZmKCk7CiAgICAKICAgIGNhdCgnPHRhYmxlICBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+U2NhdHRlcnBsb3Q6IEJlZm9yZSByZW1vdmFsPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+U2NhdHRlcnBsb3Q6IEFmdGVyIHJlbW92YWw8L2ZvbnQ+PC90aD48L3RyPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgICMgQmVmb3JlCiAgICBjYXQoIjx0cj48dGQgYWxpZ249Y2VudGVyPjwhLS08Zm9udCBjb2xvcj0nI2ZmMDAwMCc+PGgzPlNjYXR0ZXIgcGxvdCBiZXR3ZWVuIFByb3Rlb21lIGFuZCBUcmFuc2NyaXB0b21lIEFidW5kYW5jZTwvaDM+PC9mb250PlxuLS0+IiwgJzxpbWcgc3JjPSJURV9QRV9zY2F0dGVyLnBuZyIgd2lkdGg9NjAwIGhlaWdodD02MDA+PC90ZD5cbicsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICAKICAgICMgQWZ0ZXIKICAgIGNhdCgiPHRkIGFsaWduPWNlbnRlcj5cbiIsCiAgICAnPGltZyBzcmM9IkFidW5kYW5jZVBsb3Rfc2NhdHRlcl93aXRob3V0X291dGxpZXJzLnBuZyIgd2lkdGg9NjAwIGhlaWdodD02MDA+PC90ZD48L3RyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICAjQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAogICAgCiAgCiAgY29yX3Jlc3VsdF9wZWFyc29uID0gY29yLnRlc3QoUEVfVEVfZGF0YV9ub19vdXRsaWVyWywiVEVfYWJ1bmRhbmNlIl0sIFBFX1RFX2RhdGFfbm9fb3V0bGllclssIlBFX2FidW5kYW5jZSJdLCBtZXRob2QgPSAicGVhcnNvbiIpOwogIGNvcl9yZXN1bHRfc3BlYXJtYW4gPSBjb3IudGVzdChQRV9URV9kYXRhX25vX291dGxpZXJbLCJURV9hYnVuZGFuY2UiXSwgUEVfVEVfZGF0YV9ub19vdXRsaWVyWywiUEVfYWJ1bmRhbmNlIl0sIG1ldGhvZCA9ICJzcGVhcm1hbiIpOwogIGNvcl9yZXN1bHRfa2VuZGFsbCA9IGNvci50ZXN0KFBFX1RFX2RhdGFfbm9fb3V0bGllclssIlRFX2FidW5kYW5jZSJdLCBQRV9URV9kYXRhX25vX291dGxpZXJbLCJQRV9hYnVuZGFuY2UiXSwgbWV0aG9kID0gImtlbmRhbGwiKTsKICAKICBjYXQoJzx0cj48dGQ+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZD1UUlVFKTsKICBzaW5nbGVzYW1wbGVfY29yKFBFX1RFX2RhdGEsIGh0bWxvdXRmaWxlLCBhcHBlbmQ9VFJVRSk7CiAgY2F0KCc8L3RkPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQ9VFJVRSk7CiAgCiAgCiAgY2F0KCc8dGQ+PHRhYmxlIGJvcmRlcj0xIGNlbGxzcGFjaW5nPTAgY2VsbHBhZGRpbmc9NSBzdHlsZT0idGFibGUtbGF5b3V0OmF1dG87ICI+IDx0ciBiZ2NvbG9yPSIjN2EwMDE5Ij48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5QYXJhbWV0ZXI8L2ZvbnQ+PC90aD48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5NZXRob2QgMTwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPk1ldGhvZCAyPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+TWV0aG9kIDM8L2ZvbnQ+PC90aD48L3RyPlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgY2F0KAogICAgIjx0cj48dGQ+Q29ycmVsYXRpb24gbWV0aG9kPC90ZD48dGQ+Iixjb3JfcmVzdWx0X3BlYXJzb24kbWV0aG9kLCI8L3RkPjx0ZD4iLGNvcl9yZXN1bHRfc3BlYXJtYW4kbWV0aG9kLCI8L3RkPjx0ZD4iLGNvcl9yZXN1bHRfa2VuZGFsbCRtZXRob2QsIjwvdGQ+PC90cj5cbiIsCiAgICAiPHRyPjx0ZD5Db3JyZWxhdGlvbiBjb2VmZmljaWVudDwvdGQ+PHRkPiIsY29yX3Jlc3VsdF9wZWFyc29uJGVzdGltYXRlLCI8L3RkPjx0ZD4iLGNvcl9yZXN1bHRfc3BlYXJtYW4kZXN0aW1hdGUsIjwvdGQ+PHRkPiIsY29yX3Jlc3VsdF9rZW5kYWxsJGVzdGltYXRlLCI8L3RkPjwvdHI+XG4iLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKQogIGNhdCgiPC90YWJsZT48L3RkPjwvdHI+PC90YWJsZT5cbiIsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSkKICAKICAKICAKICBpZihkaW0oUEVfVEVfZGF0YV9vdXRsaWVyKVsxXTwxMCkKICB7CiAgICB0YWJfbl9yb3cgPSBkaW0oUEVfVEVfZGF0YV9vdXRsaWVyKVsxXTsKICB9ZWxzZXsKICAgIHRhYl9uX3JvdyA9IDEwOwogIH0KICAKICBjYXQoIjxicj48YnI+PGZvbnQgc2l6ZT01PjxiPjxhIGhyZWY9J1BFX1RFX2luZmx1ZW50aWFsX29ic2VydmF0aW9uLnR4dCcgdGFyZ2V0PSdfYmxhbmsnPkRvd25sb2FkIHRoZSBjb21wbGV0ZSBsaXN0IG9mIGluZmx1ZW50aWFsIG9ic2VydmF0aW9uczwvYT48L2I+PC9mb250PiZuYnNwOyZuYnNwOyZuYnNwOyZuYnNwOyIsCiAgIjxmb250IHNpemU9NT48Yj48YSBocmVmPSdQRV9URV9ub25faW5mbHVlbnRpYWxfb2JzZXJ2YXRpb24udHh0JyB0YXJnZXQ9J19ibGFuayc+RG93bmxvYWQgdGhlIGNvbXBsZXRlIGxpc3QgKEFmdGVyIHJlbW92aW5nIGluZmx1ZW50aWFsIHBvaW50cyk8L2E+PC9iPjwvZm9udD48YnI+XG4iLAogICc8YnI+PGZvbnQgY29sb3I9ImJyb3duIj48aDQ+VG9wICcsYXMuY2hhcmFjdGVyKHRhYl9uX3JvdyksJyBJbmZsdWVudGlhbCBvYnNlcnZhdGlvbnMgKENvb2tcJ3MgZGlzdGFuY2UgPiAnLGNvb2tkaXN0X3VwcGVyX2N1dG9mZiwnICogbWVhbiBDb29rXCdzIGRpc3RhbmNlKTwvaDQ+PC9mb250PlxuJywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgY2F0KCc8dGFibGUgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01PiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+XG4nLCBzZXAgPSAiIixmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIGNhdCgiPHRoPjxmb250IGNvbG9yPSNmZmNjMzM+R2VuZTwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPlByb3RlaW4gTG9nIEZvbGQtQ2hhbmdlPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+VHJhbnNjcmlwdCBMb2cgRm9sZC1DaGFuZ2U8L2ZvbnQ+PC90aD48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5Db29rJ3MgRGlzdGFuY2U8L2ZvbnQ+PC90aD48L3RyPlxuIiwKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgCiAgZm9yKGkgaW4gMTp0YWJfbl9yb3cpCiAgewogICAgY2F0KAogICAgICAnPHRyPicsJzx0ZD4nLGFzLmNoYXJhY3RlcihQRV9URV9kYXRhX291dGxpZXJfc29ydGVkW2ksMV0pLCc8L3RkPlxuJywKICAgICAgJzx0ZD4nLGZvcm1hdChQRV9URV9kYXRhX291dGxpZXJfc29ydGVkW2ksMl0sIHNjaWVudGlmaWM9RiksJzwvdGQ+XG4nLAogICAgICAnPHRkPicsUEVfVEVfZGF0YV9vdXRsaWVyX3NvcnRlZFtpLDRdLCc8L3RkPlxuJywKICAgICAgJzx0ZD4nLGZvcm1hdChQRV9URV9kYXRhX291dGxpZXJfc29ydGVkW2ksNV0sIHNjaWVudGlmaWM9RiksJzwvdGQ+PC90cj5cbicsCiAgICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgfQogICAgY2F0KCc8L3RhYmxlPjxicj48YnI+XG4nLGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICAKICAgIAp9CgoKCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgSGVhdG1hcAojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpzaW5nbGVzYW1wbGVfaGVhdG1hcD1mdW5jdGlvbihQRV9URV9kYXRhLCBodG1sb3V0ZmlsZSwgaG1fbmNsdXN0KXsKICBjYXQoJzxicj48dGFibGUgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkhlYXRtYXAgb2YgUEUgYW5kIFRFIGFidW5kYW5jZSB2YWx1ZXMgKEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nKTwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPk51bWJlciBvZiBjbHVzdGVycyB0byBleHRyYWN0OiAnLGhtX25jbHVzdCwnPC9mb250PjwvdGg+PC90cj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGhjPWhjbHVzdChkaXN0KGFzLm1hdHJpeChQRV9URV9kYXRhWyxjKCJQRV9hYnVuZGFuY2UiLCJURV9hYnVuZGFuY2UiKV0pKSkKICBobV9jbHVzdGVyID0gY3V0cmVlKGhjLGs9aG1fbmNsdXN0KTsKICAKICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvUEVfVEVfaGVhdG1hcC5wbmciLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgcG5nKG91dHBsb3QsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwLCB1bml0cyA9ICdpbicsIHJlcz0zMDApOwogICMgYml0bWFwKG91dHBsb3QsICJwbmcxNm0iKTsKICBwYXIobWZyb3c9YygxLDEpKTsKICBobWFwID0gaGVhdG1hcC4yKGFzLm1hdHJpeChQRV9URV9kYXRhWyxjKCJQRV9hYnVuZGFuY2UiLCJURV9hYnVuZGFuY2UiKV0pLCB0cmFjZT0ibm9uZSIsIGNleENvbD0xLCBjb2w9Z3JlZW5yZWQoMTAwKSxDb2x2PUYsIGxhYkNvbD1jKCJQcm90ZWlucyIsIlRyYW5zY3JpcHRzIiksIHNjYWxlPSJjb2wiLCBoY2x1c3RmdW4gPSBoY2x1c3QsIGRpc3RmdW4gPSBkaXN0KTsKICBkZXYub2ZmKCk7CiAgCiAgY2F0KCc8dHI+PHRkIGFsaWduPWNlbnRlciBjb2xzcGFuPSIyIj48aW1nIHNyYz0iUEVfVEVfaGVhdG1hcC5wbmciIHdpZHRoPTgwMCBoZWlnaHQ9ODAwPjwvdGQ+PC90cj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIAogIHRlbXBfUEVfVEVfZGF0YSA9IGRhdGEuZnJhbWUoUEVfVEVfZGF0YSRQRV9JRCwgUEVfVEVfZGF0YSRURV9hYnVuZGFuY2UsIFBFX1RFX2RhdGEkUEVfYWJ1bmRhbmNlLCBobV9jbHVzdGVyKTsKICBjb2xuYW1lcyh0ZW1wX1BFX1RFX2RhdGEpID0gYygiR2VuZSIsICJUcmFuc2NyaXB0IGFidW5kYW5jZSIsICJQcm90ZWluIGFidW5kYW5jZSIsICJDbHVzdGVyIChIaWVyYXJjaGljYWwgY2x1c3RlcmluZykiKQogIHRlbXBvdXRmaWxlID0gcGFzdGUob3V0ZGlyLCIvUEVfVEVfaGNfY2x1c3RlcnBvaW50cy50eHQiLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgd3JpdGUudGFibGUodGVtcF9QRV9URV9kYXRhLCBmaWxlPXRlbXBvdXRmaWxlLCByb3cubmFtZXM9RiwgcXVvdGU9Riwgc2VwPSJcdCIsIGVvbD0iXG4iKQogIAogIAogIGNhdCgnPHRyPjx0ZCBjb2xzcGFuPSIyIiBhbGlnbj1jZW50ZXI+PGZvbnQgc2l6ZT01PjxhIGhyZWY9IlBFX1RFX2hjX2NsdXN0ZXJwb2ludHMudHh0IiB0YXJnZXQ9Il9ibGFuayI+PGI+RG93bmxvYWQgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyIGxpc3Q8L2I+PC9hPjwvZm9udD48L3RkPjwvdHI+PC90YWJsZT5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwp9CgoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBLLW1lYW5zIGNsdXN0ZXJpbmcKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0Kc2luZ2xlc2FtcGxlX2ttZWFucz1mdW5jdGlvbihQRV9URV9kYXRhLCBodG1sb3V0ZmlsZSwgbmNsdXN0KXsKICBQRV9URV9kYXRhX2tkYXRhID0gUEVfVEVfZGF0YTsKICBrMSA9IGttZWFucyhQRV9URV9kYXRhX2tkYXRhWyxjKCJQRV9hYnVuZGFuY2UiLCJURV9hYnVuZGFuY2UiKV0sIG5jbHVzdCk7CiAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL1BFX1RFX2ttZWFucy5wbmciLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgcG5nKG91dHBsb3QsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwLCB1bml0cyA9ICdpbicsIHJlcz0zMDApOwogICMgYml0bWFwKG91dHBsb3QsICJwbmcxNm0iKTsKICBwYXIobWZyb3c9YygxLDEpKTsKICBzY2F0dGVyLnNtb290aChQRV9URV9kYXRhX2tkYXRhWywiVEVfYWJ1bmRhbmNlIl0sIFBFX1RFX2RhdGFfa2RhdGFbLCJQRV9hYnVuZGFuY2UiXSwgeGxhYj0iVHJhbnNjcmlwdCBBYnVuZGFuY2UiLCB5bGFiPSJQcm90ZWluIEFidW5kYW5jZSIsIGNleC5sYWI9MS41KTsKICBsZWdlbmQoMSwgOTUsIGxlZ2VuZD1jKCJDbHVzdGVyIDEiLCAiTGluZSAyIiksIGNvbD0icmVkIiwgbHR5PTE6MSwgY2V4PTAuOCkKICBsZWdlbmQoMSwgOTUsIGxlZ2VuZD0iQ2x1c3RlciAyIiwgY29sPSJncmVlbiIsIGx0eT0xOjEsIGNleD0wLjgpCiAgCiAgCiAgaW5kPXdoaWNoKGsxJGNsdXN0ZXI9PTEpOwogIHBvaW50cyhQRV9URV9kYXRhX2tkYXRhW2luZCwiVEVfYWJ1bmRhbmNlIl0sIFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJQRV9hYnVuZGFuY2UiXSwgY29sPSJyZWQiLCBwY2g9MTYpOwogIGluZD13aGljaChrMSRjbHVzdGVyPT0yKTsKICBwb2ludHMoUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlRFX2FidW5kYW5jZSJdLCBQRV9URV9kYXRhX2tkYXRhW2luZCwiUEVfYWJ1bmRhbmNlIl0sIGNvbD0iZ3JlZW4iLCBwY2g9MTYpOwogIGluZD13aGljaChrMSRjbHVzdGVyPT0zKTsKICBwb2ludHMoUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlRFX2FidW5kYW5jZSJdLCBQRV9URV9kYXRhX2tkYXRhW2luZCwiUEVfYWJ1bmRhbmNlIl0sIGNvbD0iYmx1ZSIsIHBjaD0xNik7CiAgaW5kPXdoaWNoKGsxJGNsdXN0ZXI9PTQpOwogIHBvaW50cyhQRV9URV9kYXRhX2tkYXRhW2luZCwiVEVfYWJ1bmRhbmNlIl0sIFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJQRV9hYnVuZGFuY2UiXSwgY29sPSJjeWFuIiwgcGNoPTE2KTsKICBpbmQ9d2hpY2goazEkY2x1c3Rlcj09NSk7CiAgcG9pbnRzKFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJURV9hYnVuZGFuY2UiXSwgUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlBFX2FidW5kYW5jZSJdLCBjb2w9ImJsYWNrIiwgcGNoPTE2KTsKICBpbmQ9d2hpY2goazEkY2x1c3Rlcj09Nik7CiAgcG9pbnRzKFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJURV9hYnVuZGFuY2UiXSwgUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlBFX2FidW5kYW5jZSJdLCBjb2w9ImJyb3duIiwgcGNoPTE2KTsKICBpbmQ9d2hpY2goazEkY2x1c3Rlcj09Nyk7CiAgcG9pbnRzKFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJURV9hYnVuZGFuY2UiXSwgUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlBFX2FidW5kYW5jZSJdLCBjb2w9ImdvbGQiLCBwY2g9MTYpOwogIGluZD13aGljaChrMSRjbHVzdGVyPT04KTsKICBwb2ludHMoUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlRFX2FidW5kYW5jZSJdLCBQRV9URV9kYXRhX2tkYXRhW2luZCwiUEVfYWJ1bmRhbmNlIl0sIGNvbD0idGhpc3RsZSIsIHBjaD0xNik7CiAgaW5kPXdoaWNoKGsxJGNsdXN0ZXI9PTkpOwogIHBvaW50cyhQRV9URV9kYXRhX2tkYXRhW2luZCwiVEVfYWJ1bmRhbmNlIl0sIFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJQRV9hYnVuZGFuY2UiXSwgY29sPSJ5ZWxsb3ciLCBwY2g9MTYpOwogIGluZD13aGljaChrMSRjbHVzdGVyPT0xMCk7CiAgcG9pbnRzKFBFX1RFX2RhdGFfa2RhdGFbaW5kLCJURV9hYnVuZGFuY2UiXSwgUEVfVEVfZGF0YV9rZGF0YVtpbmQsIlBFX2FidW5kYW5jZSJdLCBjb2w9Im9yYW5nZSIsIHBjaD0xNik7CiAgZGV2Lm9mZigpOwogIAogIGNhdCgnPGJyPjxicj48dGFibGUgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkstbWVhbiBjbHVzdGVyaW5nPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+TnVtYmVyIG9mIGNsdXN0ZXJzOiAnLG5jbHVzdCwnPC9mb250PjwvdGg+PC90cj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIHRlbXBpbmQgPSBvcmRlcihrMSRjbHVzdGVyKTsKICB0ZW1wb3V0ZmlsZSA9IHBhc3RlKG91dGRpciwiL1BFX1RFX2ttZWFuc19jbHVzdGVycG9pbnRzLnR4dCIsc2VwPSIiLGNvbGxhcHNlPSIiKTsKICB3cml0ZS50YWJsZShkYXRhLmZyYW1lKFBFX1RFX2RhdGFfa2RhdGFbdGVtcGluZCwgXSwgQ2x1c3Rlcj1rMSRjbHVzdGVyW3RlbXBpbmRdKSwgZmlsZT10ZW1wb3V0ZmlsZSwgcm93Lm5hbWVzPUYsIHF1b3RlPUYsIHNlcD0iXHQiLCBlb2w9IlxuIikKICAKICAKICBjYXQoJzx0cj48dGQgY29sc3Bhbj0iMiIgYWxpZ249Y2VudGVyPjxpbWcgc3JjPSJQRV9URV9rbWVhbnMucG5nIiB3aWR0aD04MDAgaGVpZ2h0PTgwMD48L3RkPjwvdHI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICBjYXQoJzx0cj48dGQgY29sc3Bhbj0iMiIgYWxpZ249Y2VudGVyPjxmb250IHNpemU9NT48YSBocmVmPSJQRV9URV9rbWVhbnNfY2x1c3RlcnBvaW50cy50eHQiIHRhcmdldD0iX2JsYW5rIj48Yj5Eb3dubG9hZCB0aGUgY2x1c3RlciBsaXN0PC9iPjwvYT48L2ZvbnQ+PC90ZD48L3RyPjwvdGFibGU+PGJyPjxoci8+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKfQoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBzY2F0dGVyIHBsb3QKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0Kc2luZ2xlc2FtcGxlX3NjYXR0ZXIgPSBmdW5jdGlvbihQRV9URV9kYXRhLCBvdXRmaWxlKQp7CiAgbWluX2xpbSA9IG1pbihjKFBFX1RFX2RhdGEkUEVfYWJ1bmRhbmNlLFBFX1RFX2RhdGEkVEVfYWJ1bmRhbmNlKSk7CiAgbWF4X2xpbSA9IG1heChjKFBFX1RFX2RhdGEkUEVfYWJ1bmRhbmNlLFBFX1RFX2RhdGEkVEVfYWJ1bmRhbmNlKSk7CiAgcG5nKG91dGZpbGUsIHdpZHRoID0gMTAsIGhlaWdodCA9IDEwLCB1bml0cyA9ICdpbicsIHJlcz0zMDApOwogICMgYml0bWFwKG91dGZpbGUsICJwbmcxNm0iKTsKICBnID0gZ2dwbG90KFBFX1RFX2RhdGEsIGFlcyh4PVRFX2FidW5kYW5jZSwgeT1QRV9hYnVuZGFuY2UpKStnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aCgpICsgeGxhYigiVHJhbnNjcmlwdCBhYnVuZGFuY2UgbG9nIGZvbGQtY2hhbmdlIikgKyB5bGFiKCJQcm90ZWluIGFidW5kYW5jZSBsb2cgZm9sZC1jaGFuZ2UiKSArIHhsaW0obWluX2xpbSxtYXhfbGltKSArIHlsaW0obWluX2xpbSxtYXhfbGltKTsKICBzdXBwcmVzc01lc3NhZ2VzKHBsb3QoZykpOwogICMgcGxvdChnKTsKICBkZXYub2ZmKCk7Cn0KCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQ29ycmVsYXRpb24gdGFibGUKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0Kc2luZ2xlc2FtcGxlX2NvciA9IGZ1bmN0aW9uKFBFX1RFX2RhdGEsIGh0bWxvdXRmaWxlLCBhcHBlbmQ9VFJVRSkKewogIGNvcl9yZXN1bHRfcGVhcnNvbiA9IGNvci50ZXN0KFBFX1RFX2RhdGEkVEVfYWJ1bmRhbmNlLCBQRV9URV9kYXRhJFBFX2FidW5kYW5jZSwgbWV0aG9kID0gInBlYXJzb24iKTsKICBjb3JfcmVzdWx0X3NwZWFybWFuID0gY29yLnRlc3QoUEVfVEVfZGF0YSRURV9hYnVuZGFuY2UsIFBFX1RFX2RhdGEkUEVfYWJ1bmRhbmNlLCBtZXRob2QgPSAic3BlYXJtYW4iKTsKICBjb3JfcmVzdWx0X2tlbmRhbGwgPSBjb3IudGVzdChQRV9URV9kYXRhJFRFX2FidW5kYW5jZSwgUEVfVEVfZGF0YSRQRV9hYnVuZGFuY2UsIG1ldGhvZCA9ICJrZW5kYWxsIik7CiAgCiAgY2F0KAogICAgJzx0YWJsZSAgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPlBhcmFtZXRlcjwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPk1ldGhvZCAxPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+TWV0aG9kIDI8L2ZvbnQ+PC90aD48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5NZXRob2QgMzwvZm9udD48L3RoPjwvdHI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKICBjYXQoCiAgICAiPHRyPjx0ZD5Db3JyZWxhdGlvbiBtZXRob2Q8L3RkPjx0ZD4iLGNvcl9yZXN1bHRfcGVhcnNvbiRtZXRob2QsIjwvdGQ+PHRkPiIsY29yX3Jlc3VsdF9zcGVhcm1hbiRtZXRob2QsIjwvdGQ+PHRkPiIsY29yX3Jlc3VsdF9rZW5kYWxsJG1ldGhvZCwiPC90ZD48L3RyPlxuIiwKICAgICI8dHI+PHRkPkNvcnJlbGF0aW9uIGNvZWZmaWNpZW50PC90ZD48dGQ+Iixjb3JfcmVzdWx0X3BlYXJzb24kZXN0aW1hdGUsIjwvdGQ+PHRkPiIsY29yX3Jlc3VsdF9zcGVhcm1hbiRlc3RpbWF0ZSwiPC90ZD48dGQ+Iixjb3JfcmVzdWx0X2tlbmRhbGwkZXN0aW1hdGUsIjwvdGQ+PC90cj5cbiIsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpCiAgY2F0KCI8L3RhYmxlPlxuIiwgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAKfQoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBNZWFuIG9yIE1lZGlhbiBvZiBSZXBsaWNhdGVzCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgptZXJnZVJlcGxpY2F0ZXMgPSBmdW5jdGlvbihURV9kZixQRV9kZiwgc2FtcGxlaW5mb19kZiwgbWV0aG9kKQp7CmdycHMgPSB1bmlxdWUoc2FtcGxlaW5mb19kZlssMl0pOwoKVEVfZGZfbWVyZ2VkIDw8LSBzYXBwbHkoZ3JwcywgZnVuY3Rpb24oeCl7CiAgdGVtcHNhbXBsZSA9IHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZiRHcm91cD09eCksMV0KICBpZihsZW5ndGgodGVtcHNhbXBsZSkhPTEpewogICAgYXBwbHkoVEVfZGZbLHRlbXBzYW1wbGVdLDEsbWV0aG9kKTsKICB9ZWxzZXsKICAgIHJldHVybihURV9kZlssdGVtcHNhbXBsZV0pOwogIH0KfSk7ClRFX2RmX21lcmdlZCA8PC0gICBkYXRhLmZyYW1lKGFzLmNoYXJhY3RlcihURV9kZlssMV0pLCBURV9kZl9tZXJnZWQpOwpjb2xuYW1lcyhURV9kZl9tZXJnZWQpID0gYyhjb2xuYW1lcyhURV9kZilbMV0sIGdycHMpOwoKUEVfZGZfbWVyZ2VkIDw8LSBzYXBwbHkoZ3JwcywgZnVuY3Rpb24oeCl7CiAgdGVtcHNhbXBsZSA9IHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZiRHcm91cD09eCksMV0KICBpZihsZW5ndGgodGVtcHNhbXBsZSkhPTEpewogICAgYXBwbHkoUEVfZGZbLHRlbXBzYW1wbGVdLDEsbWV0aG9kKTsKICB9ZWxzZXsKICAgIHJldHVybihQRV9kZlssdGVtcHNhbXBsZV0pOwogIH0KfSk7CgpQRV9kZl9tZXJnZWQgPDwtICAgZGF0YS5mcmFtZShhcy5jaGFyYWN0ZXIoUEVfZGZbLDFdKSwgUEVfZGZfbWVyZ2VkKTsKY29sbmFtZXMoUEVfZGZfbWVyZ2VkKSA9IGMoY29sbmFtZXMoUEVfZGYpWzFdLCBncnBzKTsKCiNzYW1wbGVpbmZvX2RmX21lcmdlZCA9ICBkYXRhLmZyYW1lKFNhbXBsZSA9IGdycHMsIEdyb3VwID0gZ3Jwcywgc3RyaW5nc0FzRmFjdG9ycyA9IEYpOwpzYW1wbGVpbmZvX2RmX21lcmdlZCA9ICBkYXRhLmZyYW1lKFNhbXBsZSA9IGdycHMsIEdyb3VwID0gIkdyb3VwIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpOwoKcmV0dXJuKGxpc3QoVEVfZGZfbWVyZ2VkID0gVEVfZGZfbWVyZ2VkLCBQRV9kZl9tZXJnZWQgPSBQRV9kZl9tZXJnZWQsIHNhbXBsZWluZm9fZGZfbWVyZ2VkID0gc2FtcGxlaW5mb19kZl9tZXJnZWQpKTsKfQoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAoVC1UZXN0IG9yIFdpbGNveG9uIHJhbmtzdW0gdGVzdCkgYW5kIFZvbGNhbm8gUGxvdAojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcGVyZm9ybV9UZXN0X1ZvbGNhbm8gPSBmdW5jdGlvbihURV9kZl9kYXRhLFBFX2RmX2RhdGEsVEVfZGZfbG9nZm9sZCwgUEVfZGZfbG9nZm9sZCxzYW1wbGVpbmZvX2RmLCBtZXRob2QsIGNvcnJlY3Rpb25fbWV0aG9kLHZvbGNfd2l0aCkKewoKUEVfY29sbmFtZXMgPSBjb2xuYW1lcyhQRV9kZl9kYXRhKTsKY29udHJvbF9zYW1wbGUgPSBzYW1wbGVpbmZvX2RmW3doaWNoKHNhbXBsZWluZm9fZGYkR3JvdXA9PSJjb250cm9sIiksMV07CmNvbnRyb2xfaW5kIDw8LSBzYXBwbHkoY29udHJvbF9zYW1wbGUsIGZ1bmN0aW9uKHgpe3RlbXBfaW5kID0gd2hpY2goUEVfY29sbmFtZXM9PXgpOyBhcy5udW1lcmljKHRlbXBfaW5kKX0pOwpjb25kaXRpb25fc2FtcGxlID0gc2FtcGxlaW5mb19kZlt3aGljaChzYW1wbGVpbmZvX2RmJEdyb3VwPT0iY2FzZSIpLDFdOwpjb25kaXRpb25faW5kIDw8LSBzYXBwbHkoY29uZGl0aW9uX3NhbXBsZSwgZnVuY3Rpb24oeCl7dGVtcF9pbmQgPSB3aGljaChQRV9jb2xuYW1lcz09eCk7IGFzLm51bWVyaWModGVtcF9pbmQpfSk7CgppZihtZXRob2Q9PSJtZWFuIil7CiAgI1BFX3B2YWwgPSBhcHBseShQRV9kZl9kYXRhWzI6bGVuZ3RoKGNvbG5hbWVzKFBFX2RmX2RhdGEpKV0sMSxmdW5jdGlvbih4KSB0LnRlc3QoeFtjb25kaXRpb25faW5kLTFdLCB4W2NvbnRyb2xfaW5kLTFdKSRwLnZhbHVlKTsKICBQRV9wdmFsID0gYXBwbHkoUEVfZGZfZGF0YVsyOmxlbmd0aChjb2xuYW1lcyhQRV9kZl9kYXRhKSldLDEsZnVuY3Rpb24oeCkge29iajwtdHJ5KHQudGVzdCh4W2NvbmRpdGlvbl9pbmQtMV0sIHhbY29udHJvbF9pbmQtMV0pLHNpbGVudD1UUlVFKTsgaWYoaXMob2JqLCAidHJ5LWVycm9yIikpe3JldHVybihOQSl9ZWxzZXtyZXR1cm4ob2JqJHAudmFsdWUpfX0pCn1lbHNlewppZihtZXRob2Q9PSJtZWRpYW4iKXsKICAgIFBFX3B2YWwgPSBhcHBseShQRV9kZl9kYXRhWzI6bGVuZ3RoKGNvbG5hbWVzKFBFX2RmX2RhdGEpKV0sMSxmdW5jdGlvbih4KSB7b2JqPC10cnkod2lsY294LnRlc3QoeFtjb25kaXRpb25faW5kLTFdLCB4W2NvbnRyb2xfaW5kLTFdKSxzaWxlbnQ9VFJVRSk7IGlmKGlzKG9iaiwgInRyeS1lcnJvciIpKXtyZXR1cm4oTkEpfWVsc2V7cmV0dXJuKG9iaiRwLnZhbHVlKX19KQogICAgIyBQRV9wdmFsID0gYXBwbHkoUEVfZGZfZGF0YVsyOmxlbmd0aChjb2xuYW1lcyhQRV9kZl9kYXRhKSldLDEsZnVuY3Rpb24oeCkgd2lsY294LnRlc3QoeFtjb25kaXRpb25faW5kLTFdLCB4W2NvbnRyb2xfaW5kLTFdKSRwLnZhbHVlKTsKICB9Cn0KUEVfYWRqX3B2YWwgPSBwLmFkanVzdChQRV9wdmFsLCBtZXRob2QgPSBjb3JyZWN0aW9uX21ldGhvZCwgbiA9IGxlbmd0aChQRV9wdmFsKSkKCgpURV9jb2xuYW1lcyA9IGNvbG5hbWVzKFRFX2RmX2RhdGEpOwpjb250cm9sX3NhbXBsZSA9IHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZiRHcm91cD09ImNvbnRyb2wiKSwxXTsKY29udHJvbF9pbmQgPDwtIHNhcHBseShjb250cm9sX3NhbXBsZSwgZnVuY3Rpb24oeCl7dGVtcF9pbmQgPSB3aGljaChURV9jb2xuYW1lcz09eCk7IGFzLm51bWVyaWModGVtcF9pbmQpfSk7CmNvbmRpdGlvbl9zYW1wbGUgPSBzYW1wbGVpbmZvX2RmW3doaWNoKHNhbXBsZWluZm9fZGYkR3JvdXA9PSJjYXNlIiksMV07CmNvbmRpdGlvbl9pbmQgPDwtIHNhcHBseShjb25kaXRpb25fc2FtcGxlLCBmdW5jdGlvbih4KXt0ZW1wX2luZCA9IHdoaWNoKFRFX2NvbG5hbWVzPT14KTsgYXMubnVtZXJpYyh0ZW1wX2luZCl9KTsKCmlmKG1ldGhvZD09Im1lYW4iKXsKICAjIFRFX3B2YWwgPSBhcHBseShURV9kZl9kYXRhWzI6bGVuZ3RoKGNvbG5hbWVzKFRFX2RmX2RhdGEpKV0sMSxmdW5jdGlvbih4KSB0LnRlc3QoeFtjb25kaXRpb25faW5kLTFdLCB4W2NvbnRyb2xfaW5kLTFdKSRwLnZhbHVlKTsKICBURV9wdmFsID0gYXBwbHkoVEVfZGZfZGF0YVsyOmxlbmd0aChjb2xuYW1lcyhURV9kZl9kYXRhKSldLDEsZnVuY3Rpb24oeCkge29iajwtdHJ5KHQudGVzdCh4W2NvbmRpdGlvbl9pbmQtMV0sIHhbY29udHJvbF9pbmQtMV0pLHNpbGVudD1UUlVFKTsgaWYoaXMob2JqLCAidHJ5LWVycm9yIikpe3JldHVybihOQSl9ZWxzZXtyZXR1cm4ob2JqJHAudmFsdWUpfX0pCn1lbHNlewogIGlmKG1ldGhvZD09Im1lZGlhbiIpewogIFRFX3B2YWwgPSBhcHBseShURV9kZl9kYXRhWzI6bGVuZ3RoKGNvbG5hbWVzKFRFX2RmX2RhdGEpKV0sMSxmdW5jdGlvbih4KSB7b2JqPC10cnkod2lsY294LnRlc3QoeFtjb25kaXRpb25faW5kLTFdLCB4W2NvbnRyb2xfaW5kLTFdKSxzaWxlbnQ9VFJVRSk7IGlmKGlzKG9iaiwgInRyeS1lcnJvciIpKXtyZXR1cm4oTkEpfWVsc2V7cmV0dXJuKG9iaiRwLnZhbHVlKX19KQogICMgVEVfcHZhbCA9IGFwcGx5KFRFX2RmX2RhdGFbMjpsZW5ndGgoY29sbmFtZXMoVEVfZGZfZGF0YSkpXSwxLGZ1bmN0aW9uKHgpIHdpbGNveC50ZXN0KHhbY29uZGl0aW9uX2luZC0xXSwgeFtjb250cm9sX2luZC0xXSkkcC52YWx1ZSk7CiAgfQp9ClRFX2Fkal9wdmFsID0gcC5hZGp1c3QoVEVfcHZhbCwgbWV0aG9kID0gY29ycmVjdGlvbl9tZXRob2QsIG4gPSBsZW5ndGgoVEVfcHZhbCkpCgoKUEVfVEVfbG9nZm9sZF9wdmFsID0gZGF0YS5mcmFtZShURV9kZl9sb2dmb2xkJEdlbmUsIFRFX2RmX2xvZ2ZvbGQkTG9nRm9sZCwgVEVfcHZhbCwgVEVfYWRqX3B2YWwsIFBFX2RmX2xvZ2ZvbGQkTG9nRm9sZCwgUEVfcHZhbCwgUEVfYWRqX3B2YWwpOwpjb2xuYW1lcyhQRV9URV9sb2dmb2xkX3B2YWwpID0gYygiR2VuZSIsICJUcmFuc2NyaXB0IGxvZyBmb2xkLWNoYW5nZSIsICJwLXZhbHVlICh0cmFuc2NyaXB0KSIsICJhZGogcC12YWx1ZSAodHJhbnNjcmlwdCkiLCAiUHJvdGVpbiBsb2cgZm9sZC1jaGFuZ2UiLCAicC12YWx1ZSAocHJvdGVpbikiLCAiYWRqIHAtdmFsdWUgKHByb3RlaW4pIik7Cm91dGRhdGFmaWxlID0gcGFzdGUob3V0ZGlyLCIvUEVfVEVfbG9nZm9sZF9wdmFsLnR4dCIsIHNlcD0iIiwgY29sbGFwc2U9IiIpOwp3cml0ZS50YWJsZShQRV9URV9sb2dmb2xkX3B2YWwsIGZpbGU9b3V0ZGF0YWZpbGUsIHJvdy5uYW1lcz1GLCBzZXA9Ilx0IiwgcXVvdGU9Rik7CmNhdCgiPGJyPjxicj48Zm9udCBzaXplPTU+PGI+PGEgaHJlZj0nUEVfVEVfbG9nZm9sZF9wdmFsLnR4dCcgdGFyZ2V0PSdfYmxhbmsnPkRvd25sb2FkIHRoZSBjb21wbGV0ZSBmb2xkIGNoYW5nZSBkYXRhIGhlcmU8L2E+PC9iPjwvZm9udD48YnI+XG4iLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKCiAgICBpZihsZW5ndGgoY29uZGl0aW9uX2luZCkhPTEpCiAgICAgICAgewogICAgICAgICMgVm9sY2FubyBQbG90CgogICAgICAgIGlmKHZvbGNfd2l0aD09ImFkal9wdmFsIikKICAgICAgICB7CiAgICAgICAgICAgIFBFX3B2YWwgPSBQRV9hZGpfcHZhbAogICAgICAgICAgICBURV9wdmFsID0gVEVfYWRqX3B2YWwKICAgICAgICAgICAgdm9sY195bGFiID0gIi1sb2cxMCBBZGp1c3RlZCBwLXZhbHVlIjsKICAgICAgICB9ZWxzZXsKICAgICAgICAgIGlmKHZvbGNfd2l0aD09InB2YWwiKQogICAgICAgICAgewogICAgICAgICAgICB2b2xjX3lsYWIgPSAiLWxvZzEwIHAtdmFsdWUiOwogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvUEVfdm9sY2Fuby5wbmciLHNlcD0iIixjb2xsYXBzZT0iIik7CiAgICAgICAgICBwbmcob3V0cGxvdCwgd2lkdGggPSAxMCwgaGVpZ2h0ID0gMTAsIHVuaXRzID0gJ2luJywgcmVzPTMwMCk7CiAgICAgICAgICAjIGJpdG1hcChvdXRwbG90LCAicG5nMTZtIik7CiAgICAgICAgICBwYXIobWZyb3c9YygxLDEpKTsKICAKICAgICAgICBwbG90KFBFX2RmX2xvZ2ZvbGQkTG9nRm9sZCwgLWxvZzEwKFBFX3B2YWwpLAogICAgICAgICAgICAgeGxhYj0ibG9nMiBmb2xkIGNoYW5nZSIsIHlsYWI9dm9sY195bGFiLAogICAgICAgICAgICAgdHlwZT0ibiIpCiAgICAgICAgc2VsIDwtIHdoaWNoKChQRV9kZl9sb2dmb2xkJExvZ0ZvbGQ8PWxvZygyLGJhc2U9MikpJihQRV9kZl9sb2dmb2xkJExvZ0ZvbGQ+PWxvZygwLjUsIGJhc2U9MikpKSAjIG9yIHdoYXRldmVyIHlvdSB3YW50IHRvIHVzZQogICAgICAgIHBvaW50cyhQRV9kZl9sb2dmb2xkW3NlbCwiTG9nRm9sZCJdLCAtbG9nMTAoUEVfcHZhbFtzZWxdKSxjb2w9ImJsYWNrIikKICAgICAgICAjc2VsIDwtIHdoaWNoKChQRV9kZl9sb2dmb2xkJExvZ0ZvbGQ+bG9nKDIsYmFzZT0yKSkmKFBFX2RmX2xvZ2ZvbGQkTG9nRm9sZDxsb2coMC41LGJhc2U9MikpKSAjIG9yIHdoYXRldmVyIHlvdSB3YW50IHRvIHVzZQogICAgICAgIHNlbCA8LSB3aGljaCgoUEVfZGZfbG9nZm9sZCRMb2dGb2xkPmxvZygyLGJhc2U9MikpfChQRV9kZl9sb2dmb2xkJExvZ0ZvbGQ8bG9nKDAuNSwgYmFzZT0yKSkpCiAgICAgICAgc2VsMSA8LSB3aGljaChQRV9wdmFsPD0wLjA1KQogICAgICAgIHNlbD1pbnRlcnNlY3Qoc2VsLHNlbDEpCiAgICAgICAgcG9pbnRzKFBFX2RmX2xvZ2ZvbGRbc2VsLCJMb2dGb2xkIl0sIC1sb2cxMChQRV9wdmFsW3NlbF0pLGNvbD0icmVkIikKICAgICAgICBzZWwgPC0gd2hpY2goKFBFX2RmX2xvZ2ZvbGQkTG9nRm9sZD5sb2coMixiYXNlPTIpKXwoUEVfZGZfbG9nZm9sZCRMb2dGb2xkPGxvZygwLjUsIGJhc2U9MikpKQogICAgICAgIHNlbDEgPC0gd2hpY2goUEVfcHZhbD4wLjA1KQogICAgICAgIHNlbD1pbnRlcnNlY3Qoc2VsLHNlbDEpCiAgICAgICAgcG9pbnRzKFBFX2RmX2xvZ2ZvbGRbc2VsLCJMb2dGb2xkIl0sIC1sb2cxMChQRV9wdmFsW3NlbF0pLGNvbD0iYmx1ZSIpCiAgICAgICAgYWJsaW5lKGggPSAtbG9nKDAuMDUsYmFzZT0xMCksIGNvbD0icmVkIiwgbHR5PTIpCiAgICAgICAgYWJsaW5lKHYgPSBsb2coMixiYXNlPTIpLCBjb2w9InJlZCIsIGx0eT0yKQogICAgICAgIGFibGluZSh2ID0gbG9nKDAuNSxiYXNlPTIpLCBjb2w9InJlZCIsIGx0eT0yKQogICAgICAgIGRldi5vZmYoKTsKICAKCgogICAgICAgIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9URV92b2xjYW5vLnBuZyIsc2VwPSIiLGNvbGxhcHNlPSIiKTsKICAgICAgICAgIHBuZyhvdXRwbG90LCB3aWR0aCA9IDEwLCBoZWlnaHQgPSAxMCwgdW5pdHMgPSAnaW4nLCByZXM9MzAwKTsKICAgICAgICAgICMgYml0bWFwKG91dHBsb3QsICJwbmcxNm0iKTsKICAgICAgICAgIHBhcihtZnJvdz1jKDEsMSkpOwoKICAgICAgICBwbG90KFRFX2RmX2xvZ2ZvbGQkTG9nRm9sZCwgLWxvZzEwKFRFX3B2YWwpLAogICAgICAgICAgICAgeGxhYj0ibG9nMiBmb2xkIGNoYW5nZSIsIHlsYWI9dm9sY195bGFiLAogICAgICAgICAgICAgdHlwZT0ibiIpCiAgICAgICAgc2VsIDwtIHdoaWNoKChURV9kZl9sb2dmb2xkJExvZ0ZvbGQ8PWxvZygyLGJhc2U9MikpJihURV9kZl9sb2dmb2xkJExvZ0ZvbGQ+PWxvZygwLjUsIGJhc2U9MikpKSAjIG9yIHdoYXRldmVyIHlvdSB3YW50IHRvIHVzZQogICAgICAgIHBvaW50cyhURV9kZl9sb2dmb2xkW3NlbCwiTG9nRm9sZCJdLCAtbG9nMTAoVEVfcHZhbFtzZWxdKSxjb2w9ImJsYWNrIikKICAgICAgICAjc2VsIDwtIHdoaWNoKChURV9kZl9sb2dmb2xkJExvZ0ZvbGQ+bG9nKDIsYmFzZT0yKSkmKFRFX2RmX2xvZ2ZvbGQkTG9nRm9sZDxsb2coMC41LGJhc2U9MikpKSAjIG9yIHdoYXRldmVyIHlvdSB3YW50IHRvIHVzZQogICAgICAgIHNlbCA8LSB3aGljaCgoVEVfZGZfbG9nZm9sZCRMb2dGb2xkPmxvZygyLGJhc2U9MikpfChURV9kZl9sb2dmb2xkJExvZ0ZvbGQ8bG9nKDAuNSwgYmFzZT0yKSkpCiAgICAgICAgc2VsMSA8LSB3aGljaChURV9wdmFsPD0wLjA1KQogICAgICAgIHNlbD1pbnRlcnNlY3Qoc2VsLHNlbDEpCiAgICAgICAgcG9pbnRzKFRFX2RmX2xvZ2ZvbGRbc2VsLCJMb2dGb2xkIl0sIC1sb2cxMChURV9wdmFsW3NlbF0pLGNvbD0icmVkIikKICAgICAgICBzZWwgPC0gd2hpY2goKFRFX2RmX2xvZ2ZvbGQkTG9nRm9sZD5sb2coMixiYXNlPTIpKXwoVEVfZGZfbG9nZm9sZCRMb2dGb2xkPGxvZygwLjUsIGJhc2U9MikpKQogICAgICAgIHNlbDEgPC0gd2hpY2goVEVfcHZhbD4wLjA1KQogICAgICAgIHNlbD1pbnRlcnNlY3Qoc2VsLHNlbDEpCiAgICAgICAgcG9pbnRzKFRFX2RmX2xvZ2ZvbGRbc2VsLCJMb2dGb2xkIl0sIC1sb2cxMChURV9wdmFsW3NlbF0pLGNvbD0iYmx1ZSIpCiAgICAgICAgYWJsaW5lKGggPSAtbG9nKDAuMDUsYmFzZT0xMCksIGNvbD0icmVkIiwgbHR5PTIpCiAgICAgICAgYWJsaW5lKHYgPSBsb2coMixiYXNlPTIpLCBjb2w9InJlZCIsIGx0eT0yKQogICAgICAgIGFibGluZSh2ID0gbG9nKDAuNSxiYXNlPTIpLCBjb2w9InJlZCIsIGx0eT0yKQogICAgICAgIGRldi5vZmYoKTsKCgoKICAgICAgICBjYXQoJzxicj48dGFibGUgIGJvcmRlcj0xIGNlbGxzcGFjaW5nPTAgY2VsbHBhZGRpbmc9NSBzdHlsZT0idGFibGUtbGF5b3V0OmF1dG87ICI+IDx0ciBiZ2NvbG9yPSIjN2EwMDE5Ij48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5UcmFuc2NyaXB0IEZvbGQtQ2hhbmdlPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+UHJvdGVpbiBGb2xkLUNoYW5nZTwvZm9udD48L3RoPjwvdHI+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgICAgICAgICBjYXQoIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iVEVfdm9sY2Fuby5wbmciIHdpZHRoPTYwMCBoZWlnaHQ9NjAwPjwvdGQ+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgICAgICAgICBjYXQoIjx0ZCBhbGlnbj1jZW50ZXI+IiwKICAgICAgICAgICAgJzxpbWcgc3JjPSJQRV92b2xjYW5vLnBuZyIgd2lkdGg9NjAwIGhlaWdodD02MDA+PC90ZD48L3RyPjwvdGFibGU+PGJyPlxuJywKICAgICAgICAgICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIH1lbHNlewogICAgICAgIGNhdCgnPGJyPjxicj48Yj48Zm9udCBjb2xvcj1yZWQ+ISEhIE5vIHJlcGxpY2F0ZXMgZm91bmQuIENhbm5vdCBwZXJmb3JtIHRlc3QgdG8gY2hlY2sgc2lnbmlmaWNhbmNlIG9mIGRpZmZlcmVudGlhbCBleHByZXNzaW9uLiBUaHVzLCBubyBWb2xjYW5vIHBsb3QgZ2VuZXJhdGVkICEhITwvZm9udD48L2I+PGJyPjxicj4nLAogICAgICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICB9Cgp9CgoKIyoqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKgojIEZ1bmN0aW9uczogRW5kCiMqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioKCmBgYAoKYGBge3J9CnAgPC0gcGxvdF9seSh5ID0gfnJub3JtKDUwKSwgdHlwZSA9ImJveCIpICU+JQogICAgICAgIGFkZF90cmFjZSh5ID0gfnJub3JtKDUwLCAxKSkKcApodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhc193aWRnZXQocCksICJib3hwbG90LXRlc3QuaHRtbCIpCmBgYAoKCmBgYCB7cn0KCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQm94cGxvdAojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQptdWx0aXNhbXBsZV9ib3hwbG90ID0gZnVuY3Rpb24oZGYsIHNhbXBsZWluZm9fZGYsIG91dGZpbGUsIGZpbGxfbGVnLCB1c2VyX3hsYWIsIHVzZXJfeWxhYikKewogIHRlbXBkZiA9IGRmWywtMSwgZHJvcD1GQUxTRV07CiAgdGVtcGRmID0gdCh0ZW1wZGYpICU+JSBhcy5kYXRhLmZyYW1lKCk7CiAgdGVtcGRmW2lzLm5hKHRlbXBkZildID0gMDsKICB0ZW1wZGYkU2FtcGxlID0gcm93bmFtZXModGVtcGRmKTsKICB0ZW1wZGYxID0gbWVsdCh0ZW1wZGYsIGlkLnZhcnMgPSAiU2FtcGxlIik7CiAgdGVtcGRmMSRHcm91cCA9IHNhbXBsZWluZm9fZGZbdGVtcGRmMSRTYW1wbGUsMl07CiAgcG5nKG91dHBsb3QsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNiwgdW5pdHMgPSAnaW4nLCByZXM9MzAwKTsKICAjIGJpdG1hcChvdXRwbG90LCAicG5nMTZtIik7CiAgaWYoZmlsbF9sZWc9PSJZZXMiKQogIHsKICAgIGcgPSBnZ3Bsb3QodGVtcGRmMSwgYWVzKHg9U2FtcGxlLCB5PXZhbHVlLCBmaWxsPUdyb3VwKSkgKyBnZW9tX2JveHBsb3QoKSArIGxhYnMoeD11c2VyX3hsYWIpICsgbGFicyh5PXVzZXJfeWxhYikKICAgIHAgPC0gcGxvdF9seSh5ID0gdGVtcGRmMSkKICB9ZWxzZXsKICAgIGlmKGZpbGxfbGVnPT0iTm8iKQogICAgewogICAgICB0ZW1wZGYxJEdyb3VwID0gYygiY2FzZSIsICJjb250cm9sIikKICAgICAgZyA9IGdncGxvdCh0ZW1wZGYxLCBhZXMoeD1TYW1wbGUsIHk9dmFsdWUsIGZpbGw9R3JvdXApKSArIGdlb21fYm94cGxvdCgpICsgbGFicyh4PXVzZXJfeGxhYikgKyBsYWJzKHk9dXNlcl95bGFiKQogICAgICBwIDwtIHBsb3RfbHkoeSA9IHRlbXBkZjEsIHR5cGUgPSJib3giKQogICAgICBodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhc193aWRnZXQocCksICJib3hwbG90LXRlc3QuaHRtbCIpCiAgICB9CiAgfQogIHBsb3QoZyk7CiAgZGV2Lm9mZigpOwp9CgoKCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQXJndW1lbnRzCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cm5vYXJncyA9IDEyOwojYXJncyA9IGNvbW1hbmRBcmdzKHRyYWlsaW5nT25seSA9IFRSVUUpOwphcmdzID0gYygnbXVsdGlwbGUnLAogICAgICAgICAnbWVhbicsCiAgICAgICAgICd0ZXN0LWRhdGEvZXhwX2Rlc2lnbl9maWxlLnRhYnVsYXInLAogICAgICAgICAndGVzdC1kYXRhL3Byb3RlaW5fZGF0YS50YWJ1bGFyJywKICAgICAgICAgJ3Rlc3QtZGF0YS90cmFuc2NyaXB0X2RhdGEudGFidWxhcicsCiAgICAgICAgICdCSCcsCiAgICAgICAgIDQsCiAgICAgICAgIDQsCiAgICAgICAgIDUsCiAgICAgICAgICdwdmFsJywKICAgICAgICAgJ3Rlc3QtZmlsZXMvdGVzdC1vdXRwdXQuaHRtbCcsCiAgICAgICAgICd0ZXN0LWZpbGVzLycpCmlmKGxlbmd0aChhcmdzKSAhPSBub2FyZ3MpCnsKICBzdG9wKHBhc3RlKCJQbGVhc2UgY2hlY2sgdXNhZ2UuIE51bWJlciBvZiBhcmd1bWVudHMgaXMgbm90IGVxdWFsIHRvICIsbm9hcmdzLHNlcD0iIixjb2xsYXBzZT0iIikpOwp9Cgptb2RlID0gYXJnc1sxXTsgIyAibXVsdGlwbGUiIG9yICJsb2dmb2xkIgptZXRob2QgPSBhcmdzWzJdOyAjICJtZWFuIiBvciAibWVkaWFuIgpzYW1wbGVpbmZvX2ZpbGUgPSBhcmdzWzNdOwpwcm90ZW9tZV9maWxlID0gYXJnc1s0XTsKdHJhbnNjcmlwdG9tZV9maWxlID0gYXJnc1s1XTsKY29ycmVjdGlvbl9tZXRob2QgPSBhcmdzWzZdOwpjb29rZGlzdF91cHBlcl9jdXRvZmYgPSBhcmdzWzddOwpudW1DbHVzdGVyID0gYXJnc1s4XTsKaG1fbmNsdXN0ID0gYXJnc1s5XTsKdm9sY193aXRoID0gYXJnc1sxMF07CgpodG1sb3V0ZmlsZSA9IGFyZ3NbMTFdOyAjIGh0bWwgb3V0cHV0IGZpbGUKb3V0ZGlyID0gYXJnc1sxMl07ICMgaHRtbCBzdXBwb3J0aW5nIGZpbGVzCgoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBDaGVjayBmb3IgZmlsZSBleGlzdGFuY2UKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KaWYoISBmaWxlLmV4aXN0cyhwcm90ZW9tZV9maWxlKSkKewogIHN0b3AocGFzdGUoIlByb3Rlb21lIERhdGEgZmlsZSBkb2VzIG5vdCBleGlzdHMuIFBhdGggZ2l2ZW46ICIscHJvdGVvbWVfZmlsZSxzZXA9IiIsY29sbGFwc2U9IiIpKTsKfQppZighIGZpbGUuZXhpc3RzKHRyYW5zY3JpcHRvbWVfZmlsZSkpCnsKICBzdG9wKHBhc3RlKCJUcmFuc2NyaXB0b21lIERhdGEgZmlsZSBkb2VzIG5vdCBleGlzdHMuIFBhdGggZ2l2ZW46ICIsdHJhbnNjcmlwdG9tZV9maWxlLHNlcD0iIixjb2xsYXBzZT0iIikpOwp9CgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIExvYWQgbGlicmFyeQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpvcHRpb25zKHdhcm49LTEpOwoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKTsKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZGF0YS50YWJsZSkpOwpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShncGxvdHMpKTsKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZ2dwbG90MikpOwpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShnZ2ZvcnRpZnkpKTsKI3N1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpOwoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBTZWxlY3QgbW9kZSBhbmQgcGFyc2UgZXhwZXJpbWVudCBkZXNpZ24gZmlsZQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQppZihtb2RlPT0ibXVsdGlwbGUiKQp7CiAgICBleHBEZXNpZ24gPSBmcmVhZChzYW1wbGVpbmZvX2ZpbGUsIGhlYWRlciA9IEZBTFNFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIHNlcD0iXHQiKSAlPiUgZGF0YS5mcmFtZSgpOwogICAgZXhwRGVzaWduX2NjID0gZXhwRGVzaWduWzE6MixdOwogICAgCiAgICBzYW1wbGVpbmZvX2RmID0gZXhwRGVzaWduWzM6bnJvdyhleHBEZXNpZ24pLF07CiAgICByb3duYW1lcyhzYW1wbGVpbmZvX2RmKT0xOm5yb3coc2FtcGxlaW5mb19kZik7CiAgICBjb2xuYW1lcyhzYW1wbGVpbmZvX2RmKSA9ICBjKCJTYW1wbGUiLCJHcm91cCIpOwogICAgCiAgICBjb25kaXRpb25fY29scyA9IHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZlssMl09PWV4cERlc2lnbl9jY1t3aGljaChleHBEZXNpZ25fY2NbLDFdPT0iY2FzZSIpLDJdKSwxXTsKICAgIGNvbmRpdGlvbl9nX25hbWUgPSAiY2FzZSI7CiAgICBjb250cm9sX2NvbHMgPSBzYW1wbGVpbmZvX2RmW3doaWNoKHNhbXBsZWluZm9fZGZbLDJdPT1leHBEZXNpZ25fY2Nbd2hpY2goZXhwRGVzaWduX2NjWywxXT09ImNvbnRyb2wiKSwyXSksMV07CiAgICBjb250cm9sX2dfbmFtZSA9ICJjb250cm9sIjsKICAgIHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZlssMl09PWV4cERlc2lnbl9jY1t3aGljaChleHBEZXNpZ25fY2NbLDFdPT0iY2FzZSIpLDJdKSwyXSA9ICJjYXNlIjsKICAgIHNhbXBsZWluZm9fZGZbd2hpY2goc2FtcGxlaW5mb19kZlssMl09PWV4cERlc2lnbl9jY1t3aGljaChleHBEZXNpZ25fY2NbLDFdPT0iY29udHJvbCIpLDJdKSwyXSA9ICJjb250cm9sIjsKICAgIHNhbXBsZWluZm9fZGZfb3JpZyA9IHNhbXBsZWluZm9fZGY7Cn0KCmlmKG1vZGU9PSJsb2dmb2xkIikKewogICAgc2FtcGxlaW5mb19kZiA9IGRhdGEuZnJhbWUoIlNhbXBsZSI9IGMoIkxvZ0ZvbGQiKSwgIkdyb3VwIj1jKCJGb2xkX0NoYW5nZSIpKQp9CgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFBhcnNlIFRyYW5zY3JpcHRvbWUgZGF0YQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpURV9kZl9vcmlnID0gZnJlYWQodHJhbnNjcmlwdG9tZV9maWxlLCBzZXA9Ilx0Iiwgc3RyaW5nc0FzRmFjdG9yPUYsIGhlYWRlcj1UKSAlPiUgZGF0YS5mcmFtZSgpOwppZihtb2RlPT0ibXVsdGlwbGUiKQp7CiAgICBURV9kZiA9IFRFX2RmX29yaWdbLGMoY29sbmFtZXMoVEVfZGZfb3JpZylbMV0sY29uZGl0aW9uX2NvbHMsY29udHJvbF9jb2xzKV07Cn0KaWYobW9kZT09ImxvZ2ZvbGQiKQp7CiAgICBURV9kZiA9IFRFX2RmX29yaWc7CiAgICBjb2xuYW1lcyhURV9kZikgPSBjKCJHZW5lcyIsICJMb2dGb2xkIik7Cn0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBQYXJzZSBQcm90ZW9tZSBkYXRhCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09ClBFX2RmX29yaWcgPSBmcmVhZChwcm90ZW9tZV9maWxlLCBzZXA9Ilx0Iiwgc3RyaW5nc0FzRmFjdG9yPUYsIGhlYWRlcj1UKSAlPiUgZGF0YS5mcmFtZSgpOwppZihtb2RlPT0ibXVsdGlwbGUiKQp7CiAgICBQRV9kZiA9IFBFX2RmX29yaWdbLGMoY29sbmFtZXMoUEVfZGZfb3JpZylbMV0sY29uZGl0aW9uX2NvbHMsY29udHJvbF9jb2xzKV07Cn0KaWYobW9kZT09ImxvZ2ZvbGQiKQp7CiAgICBQRV9kZiA9IFBFX2RmX29yaWc7CiAgICBjb2xuYW1lcyhQRV9kZikgPSBjKCJHZW5lcyIsICJMb2dGb2xkIik7Cn0KCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgQ3JlYXRlIGRpcmVjdG9yeSBzdHJ1Y3R1cmVzIGFuZCB0aGVuIHNldCB0aGUgd29ya2luZyBkaXJlY3RvcnkgdG8gb3V0cHV0IGRpcmVjdG9yeQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQppZighIGZpbGUuZXhpc3RzKG91dGRpcikpCnsKICBkaXIuY3JlYXRlKG91dGRpcik7Cn0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBXcml0ZSBpbml0aWFsIGRhdGEgc3VtbWFyeSBpbiBodG1sIG91dGZpbGUKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAgIGNhdCgiPGh0bWw+PGhlYWQ+PC9oZWFkPjxib2R5PlxuIiwgZmlsZSA9IGh0bWxvdXRmaWxlKTsKICAgIAogICAgY2F0KCI8aDE+PHU+UXVhblRQOiBBc3NvY2lhdGlvbiBiZXR3ZWVuIGFidW5kYW5jZSByYXRpb3Mgb2YgdHJhbnNjcmlwdCBhbmQgcHJvdGVpbjwvdT48L2gxPjxoci8+XG4iLAogICAgIjxmb250PjxoMz5JbnB1dCBkYXRhIHN1bW1hcnk8L2gzPjwvZm9udD5cbiIsCiAgICAiPHVsPlxuIiwKICAgICI8bGk+QWJicmV2aWF0aW9ucyB1c2VkOiBQRSAoUHJvdGVvbWUgZGF0YSkgYW5kIFRFIChUcmFuc2NyaXB0b21lIGRhdGEpIiwiPC9saT48YnI+XG4iLAogICAgIjxsaT5JbnB1dCBQcm90ZW9tZSBkYXRhIGRpbWVuc2lvbiAoUm93IENvbHVtbik6ICIsIGRpbShQRV9kZilbMV0sIiB4ICIsIGRpbShQRV9kZilbMl0sIjwvbGk+XG4iLAogICAgIjxsaT5JbnB1dCBUcmFuc2NyaXB0b21lIGRhdGEgZGltZW5zaW9uIChSb3cgQ29sdW1uKTogIiwgZGltKFRFX2RmKVsxXSwiIHggIiwgZGltKFRFX2RmKVsyXSwiPC9saT48L3VsPjxoci8+XG4iLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgY2F0KCI8aDMgaWQ9dGFibGVfb2ZfY29udGVudD5UYWJsZSBvZiBDb250ZW50czo8L2gzPlxuIiwKICAgICI8dWw+XG4iLAogICAgIjxsaT48YSBocmVmPSNzYW1wbGVfZGlzdD5TYW1wbGUgZGlzdHJpYnV0aW9uPC9hPjwvbGk+XG4iLAogICAgIjxsaT48YSBocmVmPSNjb3JyX2RhdGE+Q29ycmVsYXRpb248L2E+PC9saT5cbiIsCiAgICAiPGxpPjxhIGhyZWY9I3JlZ3Jlc3Npb25fZGF0YT5SZWdyZXNzaW9uIGFuYWx5c2lzPC9hPjwvbGk+XG4iLAogICAgIjxsaT48YSBocmVmPSNpbmZfb2JzPkluZmx1ZW50aWFsIG9ic2VydmF0aW9uczwvYT48L2xpPlxuIiwKICAgICI8bGk+PGEgaHJlZj0jY2x1c3Rlcl9kYXRhPkNsdXN0ZXIgYW5hbHlzaXM8L2E+PC9saT48L3VsPjxoci8+XG4iLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBGaW5kIGNvbW1vbiBzYW1wbGVzCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmNvbW1vbl9zYW1wbGVzID0gaW50ZXJzZWN0KHNhbXBsZWluZm9fZGZbLDFdLCBjb2xuYW1lcyhURV9kZilbLTFdKSAlPiUgaW50ZXJzZWN0KC4sIGNvbG5hbWVzKFBFX2RmKVstMV0pOwoKaWYobGVuZ3RoKGNvbW1vbl9zYW1wbGVzKT09MCkKewogIHN0b3AoIk5vIGNvbW1vbiBzYW1wbGVzIGZvdW5kICIpOwogIGNhdCgiPGI+UGxlYXNlIGNoZWNrIHlvdXIgZXhwZXJpbWVudCBkZXNpZ24gZmlsZS4gU2FtcGxlIG5hbWVzIChjb2x1bW4gbmFtZXMpIGluIHRoZSBUcmFuc2NyaXB0b21lIGFuZCB0aGUgUHJvdGVvbWUgZGF0YSBkbyBub3QgbWF0Y2guIDwvYj5cbiIsZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKfQoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBDcmVhdGUgc3Vic2V0cyBiYXNlZCBvbiBjb21tb24gc2FtcGxlcwojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpURV9kZiA9ICBzZWxlY3QoVEVfZGYsIDEsIGNvbW1vbl9zYW1wbGVzKTsKUEVfZGYgPSAgc2VsZWN0KFBFX2RmLCAxLCBjb21tb25fc2FtcGxlcyk7CnNhbXBsZWluZm9fZGYgPSBmaWx0ZXIoc2FtcGxlaW5mb19kZiwgU2FtcGxlICVpbiUgY29tbW9uX3NhbXBsZXMpOwpyb3duYW1lcyhzYW1wbGVpbmZvX2RmKSA9IHNhbXBsZWluZm9fZGZbLDFdOwoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBDaGVjayBmb3IgbnVtYmVyIG9mIHJvd3Mgc2ltaWxhcml0eQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQppZihucm93KFRFX2RmKSAhPSBucm93KFBFX2RmKSkKewogIHN0b3AoIk51bWJlciBvZiByb3dzIGluIFRyYW5zY3JpcHRvbWUgYW5kIFByb3Rlb21lIGRhdGEgYXJlIG5vdCBzYW1lIGkuZS4gdGhleSBhcmUgbm90IHBhaXJlZCIpOwogIGNhdCgiPGI+VGhlIGNvcnJlbGF0aW9uIGFuYWx5c2lzIGV4cGVjdHMgcGFpcmVkIFRFIGFuZCBQRSBkYXRhIGkuZS4gKGkpdGggZ2VuZS90cmFuc2NyaXB0IG9mIFRFIGZpbGUgc2hvdWxkIGNvcnJlc3BvbmQgdG8gKGkpdGggcHJvdGVpbiBvZiBQRSBmaWxlLiBJbiB0aGUgY3VycmVudCBpbnB1dCBwcm92aWRlZCB0aGVyZSBpcyBtaXNtYXRjaCBpbiB0ZXJtcyBvZiBudW1iZXIgb2Ygcm93cyBvZiBURSBhbmQgUEUgZmlsZS4gUGxlYXNlIG1ha2Ugc3VyZSB5b3UgcHJvdmlkZSBwYWlyZWQgZGF0YS48L2I+XG4iLGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7Cn0KCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgTnVtYmVyIG9mIGdyb3VwcwojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpuZ3JwcyA9IHVuaXF1ZShzYW1wbGVpbmZvX2RmWywyXSkgJT4lIGxlbmd0aCgpOwpncnBzID0gdW5pcXVlKHNhbXBsZWluZm9fZGZbLDJdKTsKbmFtZXMoZ3JwcykgPSBncnBzOwoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBDaGFuZ2UgY29sdW1uMSBuYW1lCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmNvbG5hbWVzKFRFX2RmKVsxXSA9ICJHZW5lIjsKY29sbmFtZXMoUEVfZGYpWzFdID0gIlByb3RlaW4iOwoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBUcmVhdCBtaXNzaW5nIHZhbHVlcwojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQpURV9uYWNvdW50ID0gc3VtKGlzLm5hKFRFX2RmKSk7ClBFX25hY291bnQgPSBzdW0oaXMubmEoUEVfZGYpKTsKClRFX2RmW2lzLm5hKFRFX2RmKV0gPSAwOwpQRV9kZltpcy5uYShQRV9kZildID0gMDsKCgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIERlY2lkZSBiYXNlZCBvbiBhbmFseXNpcyBtb2RlCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmlmKG1vZGU9PSJsb2dmb2xkIikKewogIGNhdCgnPGgyIGlkPSJzYW1wbGVfZGlzdCI+PGZvbnQgY29sb3I9I2ZmMDAwMD5TQU1QTEUgRElTVFJJQlVUSU9OPC9mb250PjwvaDI+XG4nLAogIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgIyBURSBCb3hwbG90CiAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL0JveF9URS5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICBjYXQoJzx0YWJsZSBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPlxuJywKICAnPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkJveHBsb3Q6IFRyYW5zY3JpcHRvbWUgZGF0YTwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkJveHBsb3Q6IFByb3Rlb21lIGRhdGE8L2ZvbnQ+PC90aD48L3RyPlxuJywKICAiPHRyPjx0ZCBhbGlnbj1jZW50ZXI+IiwgJzxpbWcgc3JjPSJCb3hfVEUucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICBtdWx0aXNhbXBsZV9ib3hwbG90KFRFX2RmLCBzYW1wbGVpbmZvX2RmLCBvdXRwbG90LCAiWWVzIiwgIlNhbXBsZXMiLCAiVHJhbnNjcmlwdCBBYnVuZGFuY2UgZGF0YSIpOwogIAogICMgUEUgQm94cGxvdAogIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9Cb3hfUEUucG5nIixzZXA9IiIsY29sbGFwZT0iIik7CiAgY2F0KCI8dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iQm94X1BFLnBuZyIgd2lkdGg9NTAwIGhlaWdodD01MDA+PC90ZD48L3RyPjwvdGFibGU+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIG11bHRpc2FtcGxlX2JveHBsb3QoUEVfZGYsIHNhbXBsZWluZm9fZGYsIG91dHBsb3QsICJZZXMiLCAiU2FtcGxlcyIsICJQcm90ZWluIEFidW5kYW5jZSBkYXRhIik7CiAgCiAgY2F0KCc8aHIvPjxoMiBpZD0iY29ycl9kYXRhIj48Zm9udCBjb2xvcj0jZmYwMDAwPkNPUlJFTEFUSU9OPC9mb250PjwvaDI+XG4nLAogIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgIyBURSBQRSBzY2F0dGVyCiAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL1RFX1BFX3NjYXR0ZXIucG5nIixzZXA9IiIsY29sbGFwZT0iIik7CiAgY2F0KCc8dGFibGUgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPlNjYXR0ZXIgcGxvdCBiZXR3ZWVuIFByb3Rlb21lIGFuZCBUcmFuc2NyaXB0b21lIEFidW5kYW5jZTwvZm9udD48L3RoPjwvdHI+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIGNhdCgiPHRyPjx0ZCBhbGlnbj1jZW50ZXI+IiwgJzxpbWcgc3JjPSJURV9QRV9zY2F0dGVyLnBuZyIgd2lkdGg9ODAwIGhlaWdodD04MDA+PC90ZD48L3RyPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICBQRV9URV9kYXRhID0gZGF0YS5mcmFtZShQRV9kZiwgVEVfZGYpOwogIGNvbG5hbWVzKFBFX1RFX2RhdGEpID0gYygiUEVfSUQiLCJQRV9hYnVuZGFuY2UiLCJURV9JRCIsIlRFX2FidW5kYW5jZSIpOwogIHNpbmdsZXNhbXBsZV9zY2F0dGVyKFBFX1RFX2RhdGEsIG91dHBsb3QpOyAgCiAgCiAgIyBURSBQRSBDb3IKICBjYXQoIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgc2luZ2xlc2FtcGxlX2NvcihQRV9URV9kYXRhLCBodG1sb3V0ZmlsZSwgYXBwZW5kPVRSVUUpOwogIGNhdCgnPGZvbnQgY29sb3I9InJlZCI+Kk5vdGUgdGhhdCA8dT5jb3JyZWxhdGlvbjwvdT4gaXMgPHU+c2Vuc2l0aXZlIHRvIG91dGxpZXJzPC91PiBpbiB0aGUgZGF0YS4gU28gaXQgaXMgaW1wb3J0YW50IHRvIGFuYWx5emUgb3V0bGllcnMvaW5mbHVlbnRpYWwgb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhLjxicj4gQmVsb3cgd2UgdXNlIDx1PkNvb2tcJ3MgZGlzdGFuY2UgYmFzZWQgYXBwcm9hY2g8L3U+IHRvIGlkZW50aWZ5IHN1Y2ggaW5mbHVlbnRpYWwgb2JzZXJ2YXRpb25zLjwvZm9udD5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIGNhdCgnPC90ZD48L3RhYmxlPicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogIAogIGNhdCgnPGhyLz48aDIgaWQ9InJlZ3Jlc3Npb25fZGF0YSI+PGZvbnQgY29sb3I9I2ZmMDAwMD5SRUdSRVNTSU9OIEFOQUxZU0lTPC9mb250PjwvaDI+XG4nLAogIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgIyBURSBQRSBSZWdyZXNzaW9uCiAgc2luZ2xlc2FtcGxlX3JlZ3Jlc3Npb24oUEVfVEVfZGF0YSxodG1sb3V0ZmlsZSwgYXBwZW5kPVRSVUUpOwogIAogIGNhdCgnPGhyLz48aDIgaWQ9ImNsdXN0ZXJfZGF0YSI+PGZvbnQgY29sb3I9I2ZmMDAwMD5DTFVTVEVSIEFOQUxZU0lTPC9mb250PjwvaDI+XG4nLAogIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgCiAgIyBURSBQRSBIZWF0bWFwCiAgc2luZ2xlc2FtcGxlX2hlYXRtYXAoUEVfVEVfZGF0YSwgaHRtbG91dGZpbGUsIGhtX25jbHVzdCk7CiAgCiAgCiAgIyBURSBQRSBDbHVzdGVyaW5nIChrbWVhbnMpCiAgc2luZ2xlc2FtcGxlX2ttZWFucyhQRV9URV9kYXRhLCBodG1sb3V0ZmlsZSwgbmNsdXN0PWFzLm51bWVyaWMobnVtQ2x1c3RlcikpCiAgCn1lbHNlewogIGlmKG1vZGU9PSJtdWx0aXBsZSIpCiAgewogICAgY2F0KCc8aDIgaWQ9InNhbXBsZV9kaXN0Ij48Zm9udCBjb2xvcj0jZmYwMDAwPlNBTVBMRSBESVNUUklCVVRJT048L2ZvbnQ+PC9oMj5cbicsCiAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgCiAgICAjIFRFIEJveHBsb3QKICAgIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9Cb3hfVEVfYWxsX3JlcC5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIGNhdCgnPHRhYmxlICBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPlxuJywKICAgICc8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+Qm94cGxvdDogVHJhbnNjcmlwdG9tZSBkYXRhPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+Qm94cGxvdDogUHJvdGVvbWUgZGF0YTwvZm9udD48L3RoPjwvdHI+XG4nLAogICAgIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iQm94X1RFX2FsbF9yZXAucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIHRlbXBfZGZfdGVfZGF0YSA9IGRhdGEuZnJhbWUoVEVfZGZbLDFdLCBsb2coVEVfZGZbLDI6bGVuZ3RoKFRFX2RmKV0pKTsKICAgIGNvbG5hbWVzKHRlbXBfZGZfdGVfZGF0YSkgPSBjb2xuYW1lcyhURV9kZik7CiAgICBtdWx0aXNhbXBsZV9ib3hwbG90KHRlbXBfZGZfdGVfZGF0YSwgc2FtcGxlaW5mb19kZiwgb3V0cGxvdCwgIlllcyIsICJTYW1wbGVzIiwgIlRyYW5zY3JpcHQgQWJ1bmRhbmNlIChsb2cpIik7CiAgICAKICAgICMgUEUgQm94cGxvdAogICAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL0JveF9QRV9hbGxfcmVwLnBuZyIsc2VwPSIiLGNvbGxhcGU9IiIpOwogICAgY2F0KCI8dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iQm94X1BFX2FsbF9yZXAucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPjwvdHI+PC90YWJsZT5cbicsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICB0ZW1wX2RmX3BlX2RhdGEgPSBkYXRhLmZyYW1lKFBFX2RmWywxXSwgbG9nKFBFX2RmWywyOmxlbmd0aChQRV9kZildKSk7CiAgICBjb2xuYW1lcyh0ZW1wX2RmX3BlX2RhdGEpID0gY29sbmFtZXMoUEVfZGYpOwogICAgbXVsdGlzYW1wbGVfYm94cGxvdCh0ZW1wX2RmX3BlX2RhdGEsIHNhbXBsZWluZm9fZGYsIG91dHBsb3QsICJZZXMiLCAiU2FtcGxlcyIsICJQcm90ZWluIEFidW5kYW5jZSAobG9nKSIpOwogICAgCiAgICAjIENhbGMgVEUgUENBCiAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvUENBX1RFX2FsbF9yZXAucG5nIixzZXA9IiIsY29sbGFwZT0iIik7CiAgICBtdWx0aXNhbXBsZV9QQ0EoVEVfZGYsIHNhbXBsZWluZm9fZGYsIG91dHBsb3QpOwoKICAgICMgQ2FsYyBQRSBQQ0EKICAgIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9QQ0FfUEVfYWxsX3JlcC5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIG11bHRpc2FtcGxlX1BDQShQRV9kZiwgc2FtcGxlaW5mb19kZiwgb3V0cGxvdCk7CiAgICAKICAgIAogICAgIyBSZXBsaWNhdGUgbW9kZQogICAgdGVtcGxpc3QgPSBtZXJnZVJlcGxpY2F0ZXMoVEVfZGYsUEVfZGYsIHNhbXBsZWluZm9fZGYsIG1ldGhvZCk7CiAgICBURV9kZiA9IHRlbXBsaXN0JFRFX2RmX21lcmdlZDsKICAgIFBFX2RmID0gdGVtcGxpc3QkUEVfZGZfbWVyZ2VkOwogICAgc2FtcGxlaW5mb19kZiA9IHRlbXBsaXN0JHNhbXBsZWluZm9fZGZfbWVyZ2VkOwogICAgcm93bmFtZXMoc2FtcGxlaW5mb19kZikgPSBzYW1wbGVpbmZvX2RmWywxXTsKICAgIAogICAgIyBURSBCb3hwbG90CiAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvQm94X1RFX3JlcC5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIGNhdCgnPGJyPjxmb250IGNvbG9yPSIjZmYwMDAwIj48aDM+U2FtcGxlIHdpc2UgZGlzdHJpYnV0aW9uIChCb3ggcGxvdCkgYWZ0ZXIgdXNpbmcgJyxtZXRob2QsJyBvbiByZXBsaWNhdGVzIDwvaDM+PC9mb250Pjx0YWJsZSBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+Qm94cGxvdDogVHJhbnNjcmlwdG9tZSBkYXRhPC9mb250PjwvdGg+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+Qm94cGxvdDogUHJvdGVvbWUgZGF0YTwvZm9udD48L3RoPjwvdHI+XG4nLAogICAgIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iQm94X1RFX3JlcC5wbmciIHdpZHRoPTUwMCBoZWlnaHQ9NTAwPjwvdGQ+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgdGVtcF9kZl90ZV9kYXRhID0gZGF0YS5mcmFtZShURV9kZlssMV0sIGxvZyhURV9kZlssMjpsZW5ndGgoVEVfZGYpXSkpOwogICAgY29sbmFtZXModGVtcF9kZl90ZV9kYXRhKSA9IGNvbG5hbWVzKFRFX2RmKTsKICAgIG11bHRpc2FtcGxlX2JveHBsb3QodGVtcF9kZl90ZV9kYXRhLCBzYW1wbGVpbmZvX2RmLCBvdXRwbG90LCAiTm8iLCAiU2FtcGxlIEdyb3VwcyIsICJNZWFuIFRyYW5zY3JpcHQgQWJ1bmRhbmNlIChsb2cpIik7CgogICAgIyBQRSBCb3hwbG90CiAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvQm94X1BFX3JlcC5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIGNhdCgiPHRkIGFsaWduPWNlbnRlcj4iLCAnPGltZyBzcmM9IkJveF9QRV9yZXAucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPjwvdHI+PC90YWJsZT5cbicsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICB0ZW1wX2RmX3BlX2RhdGEgPSBkYXRhLmZyYW1lKFBFX2RmWywxXSwgbG9nKFBFX2RmWywyOmxlbmd0aChQRV9kZildKSk7CiAgICBjb2xuYW1lcyh0ZW1wX2RmX3BlX2RhdGEpID0gY29sbmFtZXMoUEVfZGYpOwogICAgbXVsdGlzYW1wbGVfYm94cGxvdCh0ZW1wX2RmX3BlX2RhdGEsIHNhbXBsZWluZm9fZGYsIG91dHBsb3QsICJObyIsICJTYW1wbGUgR3JvdXBzIiwgIk1lYW4gUHJvdGVpbiBBYnVuZGFuY2UgKGxvZykiKTsKCiAgICAjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogICAgIyBDYWxjdWxhdGluZyBsb2cgZm9sZCBjaGFuZ2UgYW5kIHJ1bm5pbmcgdGhlICJzaW5nbGUiIGNvZGUgcGFydCAKICAgICM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgogICAgVEVfZGYgPSBkYXRhLmZyYW1lKCJHZW5lcyI9VEVfZGZbLDFdLCAiTG9nRm9sZCI9YXBwbHkoVEVfZGZbLGMod2hpY2goY29sbmFtZXMoVEVfZGYpPT1jb25kaXRpb25fZ19uYW1lKSx3aGljaChjb2xuYW1lcyhURV9kZik9PWNvbnRyb2xfZ19uYW1lKSldLDEsZnVuY3Rpb24oeCkgbG9nKHhbMV0veFsyXSxiYXNlPTIpKSk7CiAgICBQRV9kZiA9IGRhdGEuZnJhbWUoIkdlbmVzIj1QRV9kZlssMV0sICJMb2dGb2xkIj1hcHBseShQRV9kZlssYyh3aGljaChjb2xuYW1lcyhQRV9kZik9PWNvbmRpdGlvbl9nX25hbWUpLHdoaWNoKGNvbG5hbWVzKFBFX2RmKT09Y29udHJvbF9nX25hbWUpKV0sMSxmdW5jdGlvbih4KSBsb2coeFsxXS94WzJdLGJhc2U9MikpKTsKICAKICAgICAgIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAgICAgIyBUcmVhdCBtaXNzaW5nIHZhbHVlcwogICAgICAjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogIAogICAgICBURV9kZltpcy5pbmZpbml0ZShURV9kZlssMl0pLDJdID0gTkE7CiAgICAgIFBFX2RmW2lzLmluZmluaXRlKFBFX2RmWywyXSksMl0gPSBOQTsKICAgICAgVEVfZGZbaXMubmEoVEVfZGYpXSA9IDA7CiAgICAgIFBFX2RmW2lzLm5hKFBFX2RmKV0gPSAwOwoKICAgICAgc2FtcGxlaW5mb19kZiA9IGRhdGEuZnJhbWUoIlNhbXBsZSI9IGMoIkxvZ0ZvbGQiKSwgIkdyb3VwIj1jKCJGb2xkX0NoYW5nZSIpKQogICAgICAjPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQogICAgICAjIEZpbmQgY29tbW9uIHNhbXBsZXMKICAgICAgIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KICAKICAgICAgY29tbW9uX3NhbXBsZXMgPSBpbnRlcnNlY3Qoc2FtcGxlaW5mb19kZlssMV0sIGNvbG5hbWVzKFRFX2RmKVstMV0pICU+JSBpbnRlcnNlY3QoLiwgY29sbmFtZXMoUEVfZGYpWy0xXSk7CiAgICAgIFRFX2RmID0gIHNlbGVjdChURV9kZiwgMSwgY29tbW9uX3NhbXBsZXMpOwogICAgICBQRV9kZiA9ICBzZWxlY3QoUEVfZGYsIDEsIGNvbW1vbl9zYW1wbGVzKTsKICAgICAgc2FtcGxlaW5mb19kZiA9IGZpbHRlcihzYW1wbGVpbmZvX2RmLCBTYW1wbGUgJWluJSBjb21tb25fc2FtcGxlcyk7CiAgICAgIHJvd25hbWVzKHNhbXBsZWluZm9fZGYpID0gc2FtcGxlaW5mb19kZlssMV07CiAgCiAgICAjIFRFIEJveHBsb3QKICAgIG91dHBsb3QgPSBwYXN0ZShvdXRkaXIsIi9Cb3hfVEUucG5nIixzZXA9IiIsY29sbGFwZT0iIik7CiAgICBjYXQoJzxicj48Zm9udCBjb2xvcj0iI2ZmMDAwMCI+PGgzPkRpc3RyaWJ1dGlvbiAoQm94IHBsb3QpIG9mIGxvZyBmb2xkIGNoYW5nZSA8L2gzPjwvZm9udD4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgY2F0KCc8dGFibGUgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkJveHBsb3Q6IFRyYW5zY3JpcHRvbWUgZGF0YTwvZm9udD48L3RoPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPkJveHBsb3Q6IFByb3Rlb21lIGRhdGE8L2ZvbnQ+PC90aD48L3RyPlxuJywKICAgICI8dHI+PHRkIGFsaWduPWNlbnRlcj4iLCAnPGltZyBzcmM9IkJveF9URS5wbmciIHdpZHRoPTUwMCBoZWlnaHQ9NTAwPjwvdGQ+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgbXVsdGlzYW1wbGVfYm94cGxvdChURV9kZiwgc2FtcGxlaW5mb19kZiwgb3V0cGxvdCwgIlllcyIsICJTYW1wbGUgKGxvZzIoY2FzZS9jb250cm9sKSkiLCAiVHJhbnNjcmlwdCBBYnVuZGFuY2UgZm9sZC1jaGFuZ2UgKGxvZzIpIik7CiAgICAKICAgICMgUEUgQm94cGxvdAogICAgb3V0cGxvdCA9IHBhc3RlKG91dGRpciwiL0JveF9QRS5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIGNhdCgiPHRkIGFsaWduPWNlbnRlcj4iLCAnPGltZyBzcmM9IkJveF9QRS5wbmciIHdpZHRoPTUwMCBoZWlnaHQ9NTAwPjwvdGQ+PC90cj48L3RhYmxlPlxuJywgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIG11bHRpc2FtcGxlX2JveHBsb3QoUEVfZGYsIHNhbXBsZWluZm9fZGYsIG91dHBsb3QsICJZZXMiLCAiU2FtcGxlIChsb2cyKGNhc2UvY29udHJvbCkpIiwgIlByb3RlaW4gQWJ1bmRhbmNlIGZvbGQtY2hhbmdlKGxvZzIpIik7CiAgICAKICAgIAogICAgIyBMb2cgRm9sZCBEYXRhCiAgICBwZXJmb3JtX1Rlc3RfVm9sY2FubyhURV9kZl9vcmlnLFBFX2RmX29yaWcsVEVfZGYsIFBFX2RmLHNhbXBsZWluZm9fZGZfb3JpZyxtZXRob2QsY29ycmVjdGlvbl9tZXRob2Qsdm9sY193aXRoKQogICAgCiAgICAKICAgIAogICAgIyBQcmludCBQQ0EKICAgIAogICAgY2F0KCc8YnI+PGJyPjx0YWJsZSAgYm9yZGVyPTEgY2VsbHNwYWNpbmc9MCBjZWxscGFkZGluZz01IHN0eWxlPSJ0YWJsZS1sYXlvdXQ6YXV0bzsgIj4gPHRyIGJnY29sb3I9IiM3YTAwMTkiPjx0aD48Zm9udCBjb2xvcj0jZmZjYzMzPlBDQSBwbG90OiBUcmFuc2NyaXB0b21lIGRhdGE8L2ZvbnQ+PC90aD48dGg+PGZvbnQgY29sb3I9I2ZmY2MzMz5QQ0EgcGxvdDogUHJvdGVvbWUgZGF0YTwvZm9udD48L3RoPjwvdHI+XG4nLAogICAgIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iUENBX1RFX2FsbF9yZXAucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPlxuJywKICAgICI8dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iUENBX1BFX2FsbF9yZXAucG5nIiB3aWR0aD01MDAgaGVpZ2h0PTUwMD48L3RkPjwvdHI+PC90YWJsZT5cbicsIAogICAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgCiAgICAKICAgIAogICAgY2F0KCc8aHIvPjxoMiBpZD0iY29ycl9kYXRhIj48Zm9udCBjb2xvcj0jZmYwMDAwPkNPUlJFTEFUSU9OPC9mb250PjwvaDI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgIyBURSBQRSBzY2F0dGVyCiAgICBvdXRwbG90ID0gcGFzdGUob3V0ZGlyLCIvVEVfUEVfc2NhdHRlci5wbmciLHNlcD0iIixjb2xsYXBlPSIiKTsKICAgIGNhdCgnPGJyPjx0YWJsZSBib3JkZXI9MSBjZWxsc3BhY2luZz0wIGNlbGxwYWRkaW5nPTUgc3R5bGU9InRhYmxlLWxheW91dDphdXRvOyAiPiA8dHIgYmdjb2xvcj0iIzdhMDAxOSI+PHRoPjxmb250IGNvbG9yPSNmZmNjMzM+U2NhdHRlciBwbG90IGJldHdlZW4gUHJvdGVvbWUgYW5kIFRyYW5zY3JpcHRvbWUgQWJ1bmRhbmNlPC9mb250PjwvdGg+PC90cj5cbicsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICBjYXQoIjx0cj48dGQgYWxpZ249Y2VudGVyPiIsICc8aW1nIHNyYz0iVEVfUEVfc2NhdHRlci5wbmciIHdpZHRoPTgwMCBoZWlnaHQ9ODAwPjwvdGQ+XG4nLCBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgUEVfVEVfZGF0YSA9IGRhdGEuZnJhbWUoUEVfZGYsIFRFX2RmKTsKICAgIGNvbG5hbWVzKFBFX1RFX2RhdGEpID0gYygiUEVfSUQiLCJQRV9hYnVuZGFuY2UiLCJURV9JRCIsIlRFX2FidW5kYW5jZSIpOwogICAgc2luZ2xlc2FtcGxlX3NjYXR0ZXIoUEVfVEVfZGF0YSwgb3V0cGxvdCk7ICAKCiAgICAjIFRFIFBFIENvcgogICAgY2F0KCI8dHI+PHRkIGFsaWduPWNlbnRlcj5cbiIsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICBzaW5nbGVzYW1wbGVfY29yKFBFX1RFX2RhdGEsIGh0bWxvdXRmaWxlLCBhcHBlbmQ9VFJVRSk7CiAgICBjYXQoJzxmb250IGNvbG9yPSJyZWQiPipOb3RlIHRoYXQgPHU+Y29ycmVsYXRpb248L3U+IGlzIDx1PnNlbnNpdGl2ZSB0byBvdXRsaWVyczwvdT4gaW4gdGhlIGRhdGEuIFNvIGl0IGlzIGltcG9ydGFudCB0byBhbmFseXplIG91dGxpZXJzL2luZmx1ZW50aWFsIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YS48YnI+IEJlbG93IHdlIHVzZSA8dT5Db29rXCdzIGRpc3RhbmNlIGJhc2VkIGFwcHJvYWNoPC91PiB0byBpZGVudGlmeSBzdWNoIGluZmx1ZW50aWFsIG9ic2VydmF0aW9ucy48L2ZvbnQ+XG4nLAogICAgICBmaWxlID0gaHRtbG91dGZpbGUsIGFwcGVuZCA9IFRSVUUpOwogICAgY2F0KCc8L3RkPjwvdGFibGU+JywKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CiAgICAKICAgIGNhdCgnPGhyLz48aDIgaWQ9InJlZ3Jlc3Npb25fZGF0YSI+PGZvbnQgY29sb3I9I2ZmMDAwMD5SRUdSRVNTSU9OIEFOQUxZU0lTPC9mb250PjwvaDI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgIyBURSBQRSBSZWdyZXNzaW9uCiAgICBzaW5nbGVzYW1wbGVfcmVncmVzc2lvbihQRV9URV9kYXRhLGh0bWxvdXRmaWxlLCBhcHBlbmQ9VFJVRSk7CiAgICAKICAgIGNhdCgnPGhyLz48aDIgaWQ9ImNsdXN0ZXJfZGF0YSI+PGZvbnQgY29sb3I9I2ZmMDAwMD5DTFVTVEVSIEFOQUxZU0lTPC9mb250PjwvaDI+XG4nLAogICAgZmlsZSA9IGh0bWxvdXRmaWxlLCBhcHBlbmQgPSBUUlVFKTsKICAgIAogICAgI1RFIFBFIEhlYXRtYXAKICAgIHNpbmdsZXNhbXBsZV9oZWF0bWFwKFBFX1RFX2RhdGEsIGh0bWxvdXRmaWxlLCBobV9uY2x1c3QpOwogICAgCiAgICAjVEUgUEUgQ2x1c3RlcmluZyAoa21lYW5zKQogICAgc2luZ2xlc2FtcGxlX2ttZWFucyhQRV9URV9kYXRhLCBodG1sb3V0ZmlsZSwgbmNsdXN0PWFzLm51bWVyaWMobnVtQ2x1c3RlcikpCiAgICAKICB9Cn0KY2F0KCI8aDM+R28gVG86PC9oMz5cbiIsCiAgICAiPHVsPlxuIiwKICAgICI8bGk+PGEgaHJlZj0jc2FtcGxlX2Rpc3Q+U2FtcGxlIGRpc3RyaWJ1dGlvbjwvYT48L2xpPlxuIiwKICAgICI8bGk+PGEgaHJlZj0jY29ycl9kYXRhPkNvcnJlbGF0aW9uPC9hPjwvbGk+XG4iLAogICAgIjxsaT48YSBocmVmPSNyZWdyZXNzaW9uX2RhdGE+UmVncmVzc2lvbiBhbmFseXNpczwvYT48L2xpPlxuIiwKICAgICI8bGk+PGEgaHJlZj0jaW5mX29icz5JbmZsdWVudGlhbCBvYnNlcnZhdGlvbnM8L2E+PC9saT5cbiIsCiAgICAiPGxpPjxhIGhyZWY9I2NsdXN0ZXJfZGF0YT5DbHVzdGVyIGFuYWx5c2lzPC9hPjwvbGk+PC91bD5cbiIsCiAgICAiPGJyPjxhIGhyZWY9Iz5UT1A8L2E+IiwKICAgIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CmNhdCgiPC9ib2R5PjwvaHRtbD5cbiIsIGZpbGUgPSBodG1sb3V0ZmlsZSwgYXBwZW5kID0gVFJVRSk7CgpgYGAKCmBgYHtyfQoKYGBgCgoKYGBge3J9CmxpYnJhcnkoaHRtbHdpZGdldHMpCmxpYnJhcnkocGxvdGx5KQpgYGAKCgo=